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 |   @override | ||||||
|   Future<void> onLoad() async { |   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), |         borderColor: const Color(0xFF000000), | ||||||
|         borderWidth: 2.0, |         borderWidth: 2.0, | ||||||
|       ), |       ), | ||||||
|       paragraphStyle: BlockStyle( |       paragraph: BlockStyle( | ||||||
|         margin: const EdgeInsets.symmetric(vertical: 6), |  | ||||||
|         padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 6), |         padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 6), | ||||||
|         background: BackgroundStyle( |         background: BackgroundStyle( | ||||||
|           color: const Color(0xFFFFF0CB), |           color: const Color(0xFFFFF0CB), | ||||||
| @ -39,6 +38,7 @@ class MyTextComponent extends PositionComponent { | |||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|     final document = DocumentNode([ |     final document = DocumentNode([ | ||||||
|  |       HeaderNode.simple('1984', level: 1), | ||||||
|       ParagraphNode.simple( |       ParagraphNode.simple( | ||||||
|         'Anything could be true. The so-called laws of nature were nonsense.', |         '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 ' |         'out. "If he thinks he floats off the floor, and I simultaneously ' | ||||||
|         'think I can see him do it, then the thing happens."', |         'think I can see him do it, then the thing happens."', | ||||||
|       ), |       ), | ||||||
|       ParagraphNode.simple( |       ParagraphNode.group([ | ||||||
|         'Suddenly, like a lump of submerged wreckage breaking the surface of ' |         PlainTextNode( | ||||||
|         'water, the thought burst into his mind: "It doesn\'t really happen. ' |             'Suddenly, like a lump of submerged wreckage breaking the surface ' | ||||||
|         'We imagine it. It is hallucination."', |             '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( |       ParagraphNode.simple( | ||||||
|         'He pushed the thought under instantly. The fallacy was obvious. It ' |         'He pushed the thought under instantly. The fallacy was obvious. It ' | ||||||
|         'presupposed that somewhere or other, outside oneself, there was a ' |         '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) { |     if (_textRenderer is FormatterTextRenderer) { | ||||||
|       _textElement = |       _textElement = | ||||||
|           (_textRenderer as FormatterTextRenderer).formatter.format(_text); |           (_textRenderer as FormatterTextRenderer).formatter.format(_text); | ||||||
|       final measurements = _textElement!.lastLine.metrics; |       final measurements = _textElement!.metrics; | ||||||
|       _textElement!.lastLine.translate(0, measurements.ascent); |       _textElement!.translate(0, measurements.ascent); | ||||||
|       size.setValues(measurements.width, measurements.height); |       size.setValues(measurements.width, measurements.height); | ||||||
|     } else { |     } else { | ||||||
|       final expectedSize = textRenderer.measureText(_text); |       final expectedSize = textRenderer.measureText(_text); | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| import 'dart:ui'; | import 'dart:ui'; | ||||||
|  |  | ||||||
| import 'package:flame/src/text/common/text_line.dart'; | /// The [LineMetrics] object contains measurements of a text line. | ||||||
|  |  | ||||||
| /// The [LineMetrics] object contains measurements of a [TextLine]. |  | ||||||
| /// | /// | ||||||
| /// A line of text can be thought of as surrounded by a box (rect) that outlines | /// 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 | /// 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 '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'; | import 'package:meta/meta.dart'; | ||||||
|  |  | ||||||
| @internal | @internal | ||||||
| @ -10,3 +15,51 @@ double collapseMargin(double margin1, double margin2) { | |||||||
|     return (margin2 < 0) ? min(margin1, margin2) : margin1 + 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>`. | /// such as `<div>` or `<blockquote>`. | ||||||
| abstract class BlockElement extends Element { | abstract class BlockElement extends Element { | ||||||
|   BlockElement(this.width, this.height); |   BlockElement(this.width, this.height); | ||||||
|   double width; |  | ||||||
|   double height; |   final double width; | ||||||
|  |   final double height; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| import 'dart:ui'; | 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 | /// Replacement class for [TextPainterTextElement] which draws solid rectangles | ||||||
| /// instead of regular text. | /// instead of regular text. | ||||||
| @ -1,15 +1,17 @@ | |||||||
| import 'dart:ui'; | 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 | /// 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 | /// 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). | /// arbitrary pieces of text ought to be rendered). | ||||||
| /// | /// | ||||||
| /// Elements are at the final stage of the text rendering pipeline, they are | /// Elements are at the final stage of the text rendering pipeline, they are | ||||||
| /// created during the layout step. | /// created during the layout step. | ||||||
| abstract class Element { | abstract class Element { | ||||||
|  |   /// Moves the element by ([dx], [dy]) relative to its current location. | ||||||
|   void translate(double dx, double dy); |   void translate(double dx, double dy); | ||||||
|  |  | ||||||
|   /// Renders the element on the [canvas], at coordinates determined during the |   /// 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 |   /// In order to render the element at a different location, consider either | ||||||
|   /// calling the [translate] method, or applying a translation transform to the |   /// calling the [translate] method, or applying a translation transform to the | ||||||
|   /// canvas beforehand. |   /// canvas itself. | ||||||
|   void render(Canvas canvas); |   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/block_element.dart'; | ||||||
| import 'package:flame/src/text/elements/element.dart'; | import 'package:flame/src/text/elements/element.dart'; | ||||||
|  | import 'package:flutter/rendering.dart' hide TextStyle; | ||||||
|  |  | ||||||
| class GroupElement extends BlockElement { | 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; |   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 'dart:ui'; | ||||||
| 
 | 
 | ||||||
| import 'package:flame/src/text/common/line_metrics.dart'; | 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'; | import 'package:flame/src/text/elements/text_element.dart'; | ||||||
| 
 | 
 | ||||||
| class SpriteFontTextElement extends TextElement implements TextLine { | class SpriteFontTextElement extends TextElement { | ||||||
|   SpriteFontTextElement({ |   SpriteFontTextElement({ | ||||||
|     required this.source, |     required this.source, | ||||||
|     required this.transforms, |     required this.transforms, | ||||||
| @ -20,9 +19,6 @@ class SpriteFontTextElement extends TextElement implements TextLine { | |||||||
|   final Paint paint; |   final Paint paint; | ||||||
|   final LineMetrics _box; |   final LineMetrics _box; | ||||||
| 
 | 
 | ||||||
|   @override |  | ||||||
|   TextLine get lastLine => this; |  | ||||||
| 
 |  | ||||||
|   @override |   @override | ||||||
|   LineMetrics get metrics => _box; |   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'; | import 'package:flame/src/text/elements/element.dart'; | ||||||
|  |  | ||||||
| /// [TextElement] is the base class describing a span of text that has *inline* | /// [TextElement] is the base class that represents a single line of text, laid | ||||||
| /// placement rules. | /// out and prepared for rendering. | ||||||
| /// |  | ||||||
| /// 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 extends Element { | abstract class TextElement extends Element { | ||||||
|   TextLine get lastLine; |   /// The dimensions of this line. | ||||||
|  |   LineMetrics get metrics; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,29 +1,25 @@ | |||||||
| import 'dart:ui'; | import 'dart:ui'; | ||||||
| 
 | 
 | ||||||
| import 'package:flame/src/text/common/line_metrics.dart'; | 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: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) |   TextPainterTextElement(this._textPainter) | ||||||
|       : _box = LineMetrics( |       : _box = LineMetrics( | ||||||
|           ascent: _textPainter |           ascent: _textPainter.computeDistanceToActualBaseline( | ||||||
|               .computeDistanceToActualBaseline(TextBaseline.alphabetic), |             flutter.TextBaseline.alphabetic, | ||||||
|  |           ), | ||||||
|           width: _textPainter.width, |           width: _textPainter.width, | ||||||
|           height: _textPainter.height, |           height: _textPainter.height, | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|   final TextPainter _textPainter; |   final flutter.TextPainter _textPainter; | ||||||
|   final LineMetrics _box; |   final LineMetrics _box; | ||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   LineMetrics get metrics => _box; |   LineMetrics get metrics => _box; | ||||||
| 
 | 
 | ||||||
|   @override |  | ||||||
|   TextLine get lastLine => this; |  | ||||||
| 
 |  | ||||||
|   @override |   @override | ||||||
|   void translate(double dx, double dy) => _box.translate(dx, dy); |   void translate(double dx, double dy) => _box.translate(dx, dy); | ||||||
| 
 | 
 | ||||||
| @ -12,7 +12,7 @@ class FormatterTextRenderer<T extends TextFormatter> extends TextRenderer { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Vector2 measureText(String text) { |   Vector2 measureText(String text) { | ||||||
|     final box = formatter.format(text).lastLine.metrics; |     final box = formatter.format(text).metrics; | ||||||
|     return Vector2(box.width, box.height); |     return Vector2(box.width, box.height); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @ -24,8 +24,8 @@ class FormatterTextRenderer<T extends TextFormatter> extends TextRenderer { | |||||||
|     Anchor anchor = Anchor.topLeft, |     Anchor anchor = Anchor.topLeft, | ||||||
|   }) { |   }) { | ||||||
|     final txt = formatter.format(text); |     final txt = formatter.format(text); | ||||||
|     final box = txt.lastLine.metrics; |     final box = txt.metrics; | ||||||
|     txt.lastLine.translate( |     txt.translate( | ||||||
|       position.x - box.width * anchor.x, |       position.x - box.width * anchor.x, | ||||||
|       position.y - box.height * anchor.y - box.top, |       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/glyph_data.dart'; | ||||||
| import 'package:flame/src/text/common/line_metrics.dart'; | import 'package:flame/src/text/common/line_metrics.dart'; | ||||||
| import 'package:flame/src/text/common/sprite_font.dart'; | import 'package:flame/src/text/common/sprite_font.dart'; | ||||||
|  | import 'package:flame/src/text/elements/sprite_font_text_element.dart'; | ||||||
| import 'package:flame/src/text/elements/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/formatters/text_formatter.dart'; | ||||||
| import 'package:flame/src/text/inline/sprite_font_text_element.dart'; |  | ||||||
|  |  | ||||||
| class SpriteFontTextFormatter extends TextFormatter { | class SpriteFontTextFormatter extends TextFormatter { | ||||||
|   @Deprecated('Use SpriteFontTextFormatter.fromFont() instead; this ' |   @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/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'; | import 'package:flutter/rendering.dart'; | ||||||
|  |  | ||||||
| /// [TextPainterTextFormatter] applies a Flutter [TextStyle] to a string of | /// [TextPainterTextFormatter] applies a Flutter [TextStyle] to a string of | ||||||
| @ -16,7 +16,7 @@ class TextPainterTextFormatter extends TextFormatter { | |||||||
|         this.debugMode = false, |         this.debugMode = false, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   final TextStyle style; |   final TextStyle style; // NOTE: this is a Flutter TextStyle | ||||||
|   final TextDirection textDirection; |   final TextDirection textDirection; | ||||||
|   @Deprecated('Use DebugTextFormatter instead. Will be removed in 1.5.0') |   @Deprecated('Use DebugTextFormatter instead. Will be removed in 1.5.0') | ||||||
|   final bool debugMode; |   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/block_element.dart'; | ||||||
| import 'package:flame/src/text/elements/group_element.dart'; | import 'package:flame/src/text/styles/block_style.dart'; | ||||||
| import 'package:flame/src/text/elements/rect_element.dart'; | import 'package:flame/src/text/styles/document_style.dart'; | ||||||
| import 'package:flame/src/text/elements/rrect_element.dart'; | import 'package:flame/src/text/styles/flame_text_style.dart'; | ||||||
| import 'package:flame/src/text/styles/background_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 { | abstract class BlockNode { | ||||||
|   Element? makeBackground(BackgroundStyle? style, double width, double height) { |   /// The runtime style applied to this node, this will be set by [fillStyles]. | ||||||
|     if (style == null) { |   late BlockStyle style; | ||||||
|       return null; |  | ||||||
|     } |  | ||||||
|     final out = <Element>[]; |  | ||||||
|     final backgroundPaint = style.backgroundPaint; |  | ||||||
|     final borderPaint = style.borderPaint; |  | ||||||
|     final borders = style.borderWidths; |  | ||||||
|     final radius = style.borderRadius; |  | ||||||
|  |  | ||||||
|     if (backgroundPaint != null) { |   BlockElement format(double availableWidth); | ||||||
|       if (radius == 0) { |  | ||||||
|         out.add(RectElement(width, height, backgroundPaint)); |   void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle); | ||||||
|       } 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); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										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/common/utils.dart'; | ||||||
| import 'package:flame/src/text/elements/element.dart'; | import 'package:flame/src/text/elements/element.dart'; | ||||||
| import 'package:flame/src/text/elements/group_element.dart'; | import 'package:flame/src/text/elements/group_element.dart'; | ||||||
| import 'package:flame/src/text/nodes.dart'; | import 'package:flame/src/text/nodes/block_node.dart'; | ||||||
| import 'package:flame/src/text/nodes/paragraph_node.dart'; |  | ||||||
| import 'package:flame/src/text/styles/document_style.dart'; | import 'package:flame/src/text/styles/document_style.dart'; | ||||||
| import 'package:flutter/painting.dart'; | import 'package:flutter/painting.dart'; | ||||||
|  |  | ||||||
| class DocumentNode extends GroupBlockNode { | class DocumentNode { | ||||||
|   DocumentNode(super.children); |   DocumentNode(this.children); | ||||||
|  |  | ||||||
|  |   final List<BlockNode> children; | ||||||
|  |  | ||||||
|   /// Applies [style] to this document, producing an object that can be rendered |   /// Applies [style] to this document, producing an object that can be rendered | ||||||
|   /// on a canvas. Parameters [width] and [height] serve as the fallback values |   /// 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', |       'Width must be either provided explicitly or set in the stylesheet', | ||||||
|     ); |     ); | ||||||
|     final out = <Element>[]; |     final out = <Element>[]; | ||||||
|     final border = style.backgroundStyle?.borderWidths ?? EdgeInsets.zero; |     final border = style.background?.borderWidths ?? EdgeInsets.zero; | ||||||
|     final padding = style.padding; |     final padding = style.padding; | ||||||
|  |  | ||||||
|     final pageWidth = style.width ?? width!; |     final pageWidth = style.width ?? width!; | ||||||
|     final contentWidth = pageWidth - padding.horizontal - border.horizontal; |     final contentWidth = pageWidth - padding.horizontal; | ||||||
|     final horizontalOffset = padding.left + border.left; |     final horizontalOffset = padding.left; | ||||||
|     var verticalOffset = border.top; |     var verticalOffset = border.top; | ||||||
|     var currentMargin = padding.top; |     var currentMargin = padding.top; | ||||||
|     for (final node in children) { |     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); |       verticalOffset += collapseMargin(currentMargin, blockStyle.margin.top); | ||||||
|       final nodeElement = (node as ParagraphNode).format( |       final nodeElement = node.format(contentWidth); | ||||||
|         blockStyle, |  | ||||||
|         parentWidth: contentWidth, |  | ||||||
|       ); |  | ||||||
|       nodeElement.translate(horizontalOffset, verticalOffset); |       nodeElement.translate(horizontalOffset, verticalOffset); | ||||||
|       out.add(nodeElement); |       out.add(nodeElement); | ||||||
|       currentMargin = blockStyle.margin.bottom; |       currentMargin = blockStyle.margin.bottom; | ||||||
| @ -48,13 +47,13 @@ class DocumentNode extends GroupBlockNode { | |||||||
|           border.bottom, |           border.bottom, | ||||||
|     ); |     ); | ||||||
|     final background = makeBackground( |     final background = makeBackground( | ||||||
|       style.backgroundStyle, |       style.background, | ||||||
|       pageWidth, |       pageWidth, | ||||||
|       pageHeight, |       pageHeight, | ||||||
|     ); |     ); | ||||||
|     if (background != null) { |     if (background != null) { | ||||||
|       out.insert(0, background); |       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 'dart:ui'; | ||||||
| import 'package:flame/src/text/nodes/block_node.dart'; |  | ||||||
|  |  | ||||||
| class HeaderNode extends BlockNode { | import 'package:flame/src/text/nodes/plain_text_node.dart'; | ||||||
|   HeaderNode(this.child, {required this.level}); | 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; |   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/nodes/group_text_node.dart'; | ||||||
| import 'package:flame/src/text/elements/group_element.dart'; | import 'package:flame/src/text/nodes/plain_text_node.dart'; | ||||||
| import 'package:flame/src/text/formatters/text_painter_text_formatter.dart'; | import 'package:flame/src/text/nodes/text_block_node.dart'; | ||||||
| import 'package:flame/src/text/inline/text_painter_text_element.dart'; | import 'package:flame/src/text/nodes/text_node.dart'; | ||||||
| import 'package:flame/src/text/nodes.dart'; |  | ||||||
| import 'package:flame/src/text/nodes/block_node.dart'; |  | ||||||
| import 'package:flame/src/text/styles/block_style.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 { | class ParagraphNode extends TextBlockNode { | ||||||
|   ParagraphNode.simple(String text) |   ParagraphNode(super.child); | ||||||
|       : child = GroupTextNode([PlainTextNode(text)]); |  | ||||||
|  |  | ||||||
|   final GroupTextNode child; |   ParagraphNode.simple(String text) : super(PlainTextNode(text)); | ||||||
|  |  | ||||||
|   BlockElement format(BlockStyle style, {required double parentWidth}) { |   ParagraphNode.group(List<TextNode> fragments) | ||||||
|     final text = (child.children.first as PlainTextNode).text; |       : super(GroupTextNode(fragments)); | ||||||
|     final formatter = TextPainterTextFormatter( |  | ||||||
|       style: const painting.TextStyle(fontSize: 16), |  | ||||||
|     ); |  | ||||||
|     final contentWidth = parentWidth - style.padding.horizontal; |  | ||||||
|     final horizontalOffset = style.padding.left; |  | ||||||
|  |  | ||||||
|     final words = text.split(' '); |   static const defaultStyle = BlockStyle( | ||||||
|     final lines = <TextPainterTextElement>[]; |     margin: EdgeInsets.all(6), | ||||||
|     var verticalOffset = style.padding.top; |   ); | ||||||
|     var i0 = 0; |  | ||||||
|     var i1 = 1; |   @override | ||||||
|     var startNewLine = true; |   void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) { | ||||||
|     while (i1 <= words.length) { |     style = stylesheet.paragraph; | ||||||
|       final lineText = words.sublist(i0, i1).join(' '); |     final textStyle = Style.merge(parentTextStyle, style.text)!; | ||||||
|       final formattedLine = formatter.format(lineText); |     super.fillStyles(stylesheet, textStyle); | ||||||
|       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); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										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:flame/src/text/styles/style.dart'; | ||||||
| import 'package:flutter/rendering.dart'; | import 'package:flutter/rendering.dart'; | ||||||
|  | import 'package:meta/meta.dart'; | ||||||
|  |  | ||||||
|  | @immutable | ||||||
| class BackgroundStyle extends Style { | class BackgroundStyle extends Style { | ||||||
|   BackgroundStyle({ |   BackgroundStyle({ | ||||||
|     Color? color, |     Color? color, | ||||||
| @ -13,45 +15,28 @@ class BackgroundStyle extends Style { | |||||||
|           'Parameters `paint` and `color` are exclusive', |           'Parameters `paint` and `color` are exclusive', | ||||||
|         ), |         ), | ||||||
|         borderWidths = EdgeInsets.all(borderWidth ?? 0), |         borderWidths = EdgeInsets.all(borderWidth ?? 0), | ||||||
|         borderRadius = borderRadius ?? 0 { |         borderRadius = borderRadius ?? 0, | ||||||
|     if (paint != null) { |         backgroundPaint = | ||||||
|       backgroundPaint = paint; |             paint ?? (color != null ? (Paint()..color = color) : null), | ||||||
|     } else if (color != null) { |         borderPaint = borderColor != null | ||||||
|       backgroundPaint = Paint()..color = color; |             ? (Paint() | ||||||
|     } else { |               ..color = borderColor | ||||||
|       backgroundPaint = null; |               ..style = PaintingStyle.stroke | ||||||
|     } |               ..strokeWidth = borderWidth ?? 0) | ||||||
|     if (borderColor != null) { |             : null; | ||||||
|       borderPaint = Paint() |  | ||||||
|         ..color = borderColor |  | ||||||
|         ..style = PaintingStyle.stroke |  | ||||||
|         ..strokeWidth = borderWidth ?? 0; |  | ||||||
|     } else { |  | ||||||
|       borderPaint = null; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   late final Paint? backgroundPaint; |   final Paint? backgroundPaint; | ||||||
|   late final Paint? borderPaint; |   final Paint? borderPaint; | ||||||
|   final double borderRadius; |   final double borderRadius; | ||||||
|   final EdgeInsets borderWidths; |   final EdgeInsets borderWidths; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   BackgroundStyle clone() => copyWith(); |   BackgroundStyle copyWith(BackgroundStyle other) { | ||||||
|  |  | ||||||
|   BackgroundStyle copyWith({ |  | ||||||
|     Color? color, |  | ||||||
|     Paint? paint, |  | ||||||
|     Color? borderColor, |  | ||||||
|     double? borderRadius, |  | ||||||
|     double? borderWidth, |  | ||||||
|   }) { |  | ||||||
|     return BackgroundStyle( |     return BackgroundStyle( | ||||||
|       color: color ?? (paint == null ? backgroundPaint?.color : null), |       paint: other.backgroundPaint ?? backgroundPaint, | ||||||
|       paint: paint ?? backgroundPaint, |       borderColor: other.borderPaint?.color ?? borderPaint?.color, | ||||||
|       borderColor: borderColor ?? borderPaint?.color, |       borderRadius: other.borderRadius, | ||||||
|       borderRadius: borderRadius ?? this.borderRadius, |       borderWidth: other.borderWidths.top, | ||||||
|       borderWidth: borderWidth ?? borderWidths.left, |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,30 +1,36 @@ | |||||||
| import 'package:flame/src/text/styles/background_style.dart'; | 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: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 { | class BlockStyle extends Style { | ||||||
|   BlockStyle({ |   const BlockStyle({ | ||||||
|     this.margin = EdgeInsets.zero, |  | ||||||
|     this.padding = EdgeInsets.zero, |  | ||||||
|     this.background, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   EdgeInsets margin; |  | ||||||
|   EdgeInsets padding; |  | ||||||
|   BackgroundStyle? background; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   BlockStyle clone() => copyWith(); |  | ||||||
|  |  | ||||||
|   BlockStyle copyWith({ |  | ||||||
|     EdgeInsets? margin, |     EdgeInsets? margin, | ||||||
|     EdgeInsets? padding, |     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( |     return BlockStyle( | ||||||
|       margin: margin ?? this.margin, |       margin: other._margin ?? _margin, | ||||||
|       padding: padding ?? this.padding, |       padding: other._padding ?? _padding, | ||||||
|       background: background ?? this.background, |       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/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/nodes/paragraph_node.dart'; | ||||||
| import 'package:flame/src/text/styles/background_style.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/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/overflow.dart'; | ||||||
| import 'package:flame/src/text/styles/style.dart'; | import 'package:flame/src/text/styles/style.dart'; | ||||||
| import 'package:flutter/painting.dart' show EdgeInsets; | import 'package:flutter/painting.dart' show EdgeInsets; | ||||||
| import 'package:meta/meta.dart'; |  | ||||||
|  |  | ||||||
| /// [DocumentStyle] is a user-facing description of how to render an entire | /// [DocumentStyle] is a user-facing description of how to render an entire | ||||||
| /// body of text; it roughly corresponds to a stylesheet in HTML. | /// body of text; it roughly corresponds to a stylesheet in HTML. | ||||||
| @ -21,25 +22,39 @@ class DocumentStyle extends Style { | |||||||
|   DocumentStyle({ |   DocumentStyle({ | ||||||
|     this.width, |     this.width, | ||||||
|     this.height, |     this.height, | ||||||
|     EdgeInsets? padding, |     this.padding = EdgeInsets.zero, | ||||||
|     BackgroundStyle? background, |     this.background, | ||||||
|     BlockStyle? paragraphStyle, |     FlameTextStyle? text, | ||||||
|     BlockStyle? header1Style, |     FlameTextStyle? boldText, | ||||||
|     BlockStyle? header2Style, |     FlameTextStyle? italicText, | ||||||
|     BlockStyle? header3Style, |     BlockStyle? paragraph, | ||||||
|     BlockStyle? header4Style, |     BlockStyle? header1, | ||||||
|     BlockStyle? header5Style, |     BlockStyle? header2, | ||||||
|     BlockStyle? header6Style, |     BlockStyle? header3, | ||||||
|   }) : padding = padding ?? EdgeInsets.zero { |     BlockStyle? header4, | ||||||
|     backgroundStyle = acquire(background); |     BlockStyle? header5, | ||||||
|     this.paragraphStyle = acquire(paragraphStyle ?? defaultParagraphStyle)!; |     BlockStyle? header6, | ||||||
|     this.header1Style = acquire(header1Style ?? defaultHeader1Style)!; |   })  : _text = Style.merge(text, DocumentStyle.defaultTextStyle), | ||||||
|     this.header2Style = acquire(header2Style ?? defaultHeader2Style)!; |         _boldText = Style.merge(boldText, BoldTextNode.defaultStyle), | ||||||
|     this.header3Style = acquire(header3Style ?? defaultHeader3Style)!; |         _italicText = Style.merge(italicText, ItalicTextNode.defaultStyle), | ||||||
|     this.header4Style = acquire(header4Style ?? defaultHeader4Style)!; |         _paragraph = Style.merge(paragraph, ParagraphNode.defaultStyle), | ||||||
|     this.header5Style = acquire(header5Style ?? defaultHeader5Style)!; |         _header1 = Style.merge(header1, HeaderNode.defaultStyleH1), | ||||||
|     this.header6Style = acquire(header6Style ?? defaultHeader6Style)!; |         _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. |   /// 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 |   /// 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 |   /// out exceeds the provided [height]. See the [Overflow] enum description for | ||||||
|   /// more details. |   /// 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 distance from the outer edges of the page to the inner content box of | ||||||
|   /// the document. |   /// the document. | ||||||
| @ -79,89 +94,51 @@ class DocumentStyle extends Style { | |||||||
|  |  | ||||||
|   /// If present, describes what kind of background and borders to draw for the |   /// If present, describes what kind of background and borders to draw for the | ||||||
|   /// document page(s). |   /// document page(s). | ||||||
|   late final BackgroundStyle? backgroundStyle; |   final BackgroundStyle? background; | ||||||
|  |  | ||||||
|   /// Style for paragraph nodes in the document. |   FlameTextStyle get text => _text!; | ||||||
|   late final BlockStyle paragraphStyle; |   FlameTextStyle get boldText => _boldText!; | ||||||
|  |   FlameTextStyle get italicText => _italicText!; | ||||||
|  |  | ||||||
|   /// Style for level-1 headers. |   /// Style for [ParagraphNode]s. | ||||||
|   late final BlockStyle header1Style; |   BlockStyle get paragraph => _paragraph!; | ||||||
|  |  | ||||||
|   /// Style for level-2 headers. |   /// Styles for [HeaderNode]s, levels 1 to 6. | ||||||
|   late final BlockStyle header2Style; |   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. |   static FlameTextStyle defaultTextStyle = FlameTextStyle(fontSize: 16.0); | ||||||
|   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(); |  | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   DocumentStyle clone() => copyWith(); |   DocumentStyle copyWith(DocumentStyle other) { | ||||||
|  |  | ||||||
|   DocumentStyle copyWith({ |  | ||||||
|     double? width, |  | ||||||
|     double? height, |  | ||||||
|     EdgeInsets? padding, |  | ||||||
|     BackgroundStyle? background, |  | ||||||
|     BlockStyle? paragraphStyle, |  | ||||||
|     BlockStyle? header1Style, |  | ||||||
|     BlockStyle? header2Style, |  | ||||||
|     BlockStyle? header3Style, |  | ||||||
|     BlockStyle? header4Style, |  | ||||||
|     BlockStyle? header5Style, |  | ||||||
|     BlockStyle? header6Style, |  | ||||||
|   }) { |  | ||||||
|     return DocumentStyle( |     return DocumentStyle( | ||||||
|       width: width ?? this.width, |       width: other.width ?? width, | ||||||
|       height: height ?? this.height, |       height: other.height ?? height, | ||||||
|       padding: padding ?? this.padding, |       padding: other.padding, | ||||||
|       background: background ?? backgroundStyle, |       background: merge(other.background, background)! as BackgroundStyle, | ||||||
|       paragraphStyle: paragraphStyle ?? this.paragraphStyle, |       paragraph: merge(other.paragraph, paragraph)! as BlockStyle, | ||||||
|       header1Style: header1Style ?? this.header1Style, |       header1: merge(other.header1, header1)! as BlockStyle, | ||||||
|       header2Style: header2Style ?? this.header2Style, |       header2: merge(other.header2, header2)! as BlockStyle, | ||||||
|       header3Style: header3Style ?? this.header3Style, |       header3: merge(other.header3, header3)! as BlockStyle, | ||||||
|       header4Style: header4Style ?? this.header4Style, |       header4: merge(other.header4, header4)! as BlockStyle, | ||||||
|       header5Style: header5Style ?? this.header5Style, |       header5: merge(other.header5, header5)! as BlockStyle, | ||||||
|       header6Style: header6Style ?? this.header6Style, |       header6: merge(other.header6, header6)! as BlockStyle, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @internal |   final Map<Style, Map<Style, Style>> _mergedStylesCache = {}; | ||||||
|   BlockStyle styleFor(BlockNode node) { |   Style? merge(Style? style1, Style? style2) { | ||||||
|     if (node is ParagraphNode) { |     if (style1 == null) { | ||||||
|       return paragraphStyle; |       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:flame/src/text/styles/document_style.dart'; | ||||||
| import 'package:meta/meta.dart'; |  | ||||||
|  |  | ||||||
| /// A [Style] is a base class for several classes that collectively describe | /// A [Style] is a base class for several classes that collectively describe | ||||||
| /// the desired visual appearance of a "rich-text" document. | /// 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. | /// The tree of [Style]s is roughly equivalent to a CSS stylesheet. | ||||||
| abstract class Style { | abstract class Style { | ||||||
|   /// The owner of the current style. |   const 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; |  | ||||||
|  |  | ||||||
|   /// Creates and returns a copy of the current object. |   Style copyWith(covariant Style other); | ||||||
|   Style clone(); |  | ||||||
|  |  | ||||||
|   /// Marks [style] as being owned by the current object and returns it. |   static T? merge<T extends Style>(T? style1, T? style2) { | ||||||
|   /// However, if the [style] is already owned by some other object, then clones |     if (style1 == null) { | ||||||
|   /// the [style], marks the copy as being owned, and returns it. |       return style2; | ||||||
|   @protected |     } else if (style2 == null) { | ||||||
|   S? acquire<S extends Style>(S? style) { |       return style1; | ||||||
|     if (style == null) { |     } else { | ||||||
|       return null; |       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/glyph_data.dart' show GlyphData; | ||||||
| export 'src/text/common/line_metrics.dart' show LineMetrics; | export 'src/text/common/line_metrics.dart' show LineMetrics; | ||||||
| export 'src/text/common/sprite_font.dart' show SpriteFont; | export 'src/text/common/sprite_font.dart' show SpriteFont; | ||||||
| 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/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_element.dart' show TextElement; | ||||||
|  | export 'src/text/elements/text_painter_text_element.dart' | ||||||
|  |     show TextPainterTextElement; | ||||||
| export 'src/text/formatters/sprite_font_text_formatter.dart' | export 'src/text/formatters/sprite_font_text_formatter.dart' | ||||||
|     show SpriteFontTextFormatter; |     show SpriteFontTextFormatter; | ||||||
| export 'src/text/formatters/text_formatter.dart' show TextFormatter; | export 'src/text/formatters/text_formatter.dart' show TextFormatter; | ||||||
| export 'src/text/formatters/text_painter_text_formatter.dart' | export 'src/text/formatters/text_painter_text_formatter.dart' | ||||||
|     show TextPainterTextFormatter; |     show TextPainterTextFormatter; | ||||||
| export 'src/text/nodes/block_node.dart' show BlockNode; | export 'src/text/nodes/block_node.dart' show BlockNode; | ||||||
|  | export 'src/text/nodes/bold_text_node.dart' show BoldTextNode; | ||||||
|  | export 'src/text/nodes/column_node.dart' show ColumnNode; | ||||||
| export 'src/text/nodes/document_node.dart' show DocumentNode; | 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/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/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/sprite_font_renderer.dart' show SpriteFontRenderer; | ||||||
| export 'src/text/styles/background_style.dart' show BackgroundStyle; | export 'src/text/styles/background_style.dart' show BackgroundStyle; | ||||||
| export 'src/text/styles/block_style.dart' show BlockStyle; | export 'src/text/styles/block_style.dart' show BlockStyle; | ||||||
| export 'src/text/styles/document_style.dart' show DocumentStyle; | export 'src/text/styles/document_style.dart' show DocumentStyle; | ||||||
|  | export 'src/text/styles/flame_text_style.dart' show FlameTextStyle; | ||||||
|  | export 'src/text/styles/style.dart' show Style; | ||||||
| export 'src/text/text_paint.dart' show TextPaint; | export 'src/text/text_paint.dart' show TextPaint; | ||||||
| export 'src/text/text_renderer.dart' show TextRenderer; | export 'src/text/text_renderer.dart' show TextRenderer; | ||||||
|  | |||||||
| @ -1,12 +1,11 @@ | |||||||
| import 'package:flame/components.dart'; |  | ||||||
| import 'package:flame/text.dart'; | import 'package:flame/text.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/rendering.dart' as flutter; | ||||||
| import 'package:test/test.dart'; | import 'package:test/test.dart'; | ||||||
|  |  | ||||||
| void main() { | void main() { | ||||||
|   group('TextPaint', () { |   group('TextPaint', () { | ||||||
|     test('copyWith returns a new instance with the new values', () { |     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) |       final tp = TextPaint(style: style) | ||||||
|           .copyWith((t) => t.copyWith(fontFamily: 'Helvetica')); |           .copyWith((t) => t.copyWith(fontFamily: 'Helvetica')); | ||||||
|       expect(tp.style.fontSize, 12); |       expect(tp.style.fontSize, 12); | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ class DebugTextFormatter extends TextFormatter { | |||||||
|   TextElement format(String text) => _DebugTextElement(this, text); |   TextElement format(String text) => _DebugTextElement(this, text); | ||||||
| } | } | ||||||
|  |  | ||||||
| class _DebugTextElement extends TextElement implements TextLine { | class _DebugTextElement extends TextElement { | ||||||
|   _DebugTextElement(this.style, this.text) { |   _DebugTextElement(this.style, this.text) { | ||||||
|     final charWidth = style.fontSize * 1.0; |     final charWidth = style.fontSize * 1.0; | ||||||
|     final charHeight = style.fontSize; |     final charHeight = style.fontSize; | ||||||
| @ -52,9 +52,6 @@ class _DebugTextElement extends TextElement implements TextLine { | |||||||
|   @override |   @override | ||||||
|   late final LineMetrics metrics; |   late final LineMetrics metrics; | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   TextLine get lastLine => this; |  | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void render(Canvas canvas) { |   void render(Canvas canvas) { | ||||||
|     canvas.save(); |     canvas.save(); | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Pasha Stetsenko
					Pasha Stetsenko