Line data Source code
1 : import 'dart:ui';
2 :
3 : import 'package:flutter/material.dart' as material;
4 :
5 : import 'anchor.dart';
6 : import 'components/cache/memory_cache.dart';
7 : import 'components/text_component.dart';
8 : import 'extensions/size.dart';
9 : import 'extensions/vector2.dart';
10 :
11 : /// [TextRenderer] is the abstract API that Flame uses for rendering text in its features
12 : /// this class can be extended to provide an implementation of text rendering in the engine.
13 : ///
14 : /// See [TextPaint] for the default implementation offered by Flame
15 : abstract class TextRenderer<T extends BaseTextConfig> {
16 : /// A registry containing default providers for every [TextRenderer] subclass;
17 : /// used by [createDefault] to create default parameter values.
18 : ///
19 : /// If you add a new [TextRenderer] child, you can register it by adding it,
20 : /// alongisde a provider lambda, to this map.
21 3 : static Map<Type, TextRenderer Function()> defaultCreatorsRegistry = {
22 2 : TextRenderer: () => TextPaint(),
23 2 : TextPaint: () => TextPaint(),
24 : };
25 :
26 : final T config;
27 :
28 1 : TextRenderer({required this.config});
29 :
30 : /// Renders a given [text] in a given position [position] using the provided [canvas] and [anchor].
31 : ///
32 : /// Renders it in the given position, considering the [anchor] specified.
33 : /// For example, if [Anchor.center] is specified, it's going to be drawn centered around [position].
34 : ///
35 : /// Example usage (Using TextPaint implementation):
36 : ///
37 : /// const TextPaint config = TextPaint(fontSize: 48.0, fontFamily: 'Awesome Font');
38 : /// config.render(canvas, Vector2(size.x - 10, size.y - 10, anchor: Anchor.bottomRight);
39 : void render(
40 : Canvas canvas,
41 : String text,
42 : Vector2 position, {
43 : Anchor anchor = Anchor.topLeft,
44 : });
45 :
46 : /// Given a [text] String, returns the width of that [text].
47 : double measureTextWidth(String text);
48 :
49 : /// Given a [text] String, returns the height of that [text].
50 : double measureTextHeight(String text);
51 :
52 : /// Given a [text] String, returns a Vector2 with the size of that [text] has.
53 1 : Vector2 measureText(String text) {
54 1 : return Vector2(
55 1 : measureTextWidth(text),
56 1 : measureTextHeight(text),
57 : );
58 : }
59 :
60 : /// Creates a new instance of this painter but transforming the [config]
61 : /// object via the provided lambda.
62 : TextRenderer<T> copyWith(T Function(T) transform);
63 :
64 : /// Given a generic type [T], creates a default renderer of that type.
65 1 : static T createDefault<T extends TextRenderer>() {
66 2 : final creator = defaultCreatorsRegistry[T];
67 : if (creator != null) {
68 1 : return creator() as T;
69 : } else {
70 0 : throw 'Unkown implementation of TextRenderer: $T. Please register it under [defaultCreatorsRegistry].';
71 : }
72 : }
73 : }
74 :
75 : /// A Text Config contains all typographical information required to render texts; i.e., font size, text direction, etc.
76 : abstract class BaseTextConfig {
77 : /// The font size to be used, in points.
78 : final double fontSize;
79 :
80 : /// The direction to render this text (left to right or right to left).
81 : ///
82 : /// Normally, leave this as is for most languages.
83 : /// For proper fonts of languages like Hebrew or Arabic, replace this with [TextDirection.rtl].
84 : final TextDirection textDirection;
85 :
86 : /// The height of line, as a multiple of font size.
87 : final double? lineHeight;
88 :
89 42 : const BaseTextConfig({
90 : this.fontSize = 24.0,
91 : this.textDirection = TextDirection.ltr,
92 : this.lineHeight,
93 : });
94 : }
95 :
96 : /// An extension of the BaseTextConfig which includes more configs supported by
97 : /// TextPaint
98 : class TextPaintConfig extends BaseTextConfig {
99 : /// The font color to be used.
100 : ///
101 : /// Dart's [Color] class is just a plain wrapper on top of ARGB color (0xAARRGGBB).
102 : /// For example,
103 : ///
104 : /// const TextPaint config = TextPaint(color: const Color(0xFF00FF00)); // green
105 : ///
106 : /// You can also use your Palette class to access colors used in your game.
107 : final Color color;
108 :
109 : /// The font family to be used. You can use available by default fonts for your platform (like Arial), or you can add custom fonts.
110 : ///
111 : /// To add custom fonts, add the following code to your pubspec.yaml file:
112 : ///
113 : /// flutter:
114 : /// fonts:
115 : /// - family: 5x5
116 : /// fonts:
117 : /// - asset: assets/fonts/5x5_pixel.ttf
118 : ///
119 : /// In this example we are adding a font family that's being named '5x5' provided in the specified ttf file.
120 : /// You must provide the full path of the ttf file (from root); you should put it into your assets folder, and preferably inside a fonts folder.
121 : /// You don't need to add this together with the other assets on the flutter/assets bit.
122 : /// The name you choose for the font family can be any name (it's not inside the TTF file and the filename doesn't need to match).
123 : final String fontFamily;
124 :
125 : /// Creates a constant [TextPaint] with sensible defaults.
126 : ///
127 : /// Every parameter can be specified.
128 43 : const TextPaintConfig({
129 : this.color = const Color(0xFF000000),
130 : this.fontFamily = 'Arial',
131 : double fontSize = 24.0,
132 : TextDirection textDirection = TextDirection.ltr,
133 : double? lineHeight,
134 1 : }) : super(
135 : fontSize: fontSize,
136 : textDirection: textDirection,
137 : lineHeight: lineHeight,
138 : );
139 :
140 : /// Creates a new [TextPaintConfig] changing only the [fontSize].
141 : ///
142 : /// This does not change the original (as it's immutable).
143 1 : TextPaintConfig withFontSize(double fontSize) {
144 1 : return TextPaintConfig(
145 : fontSize: fontSize,
146 1 : color: color,
147 1 : fontFamily: fontFamily,
148 1 : textDirection: textDirection,
149 : );
150 : }
151 :
152 : /// Creates a new [TextPaintConfig] changing only the [color].
153 : ///
154 : /// This does not change the original (as it's immutable).
155 0 : TextPaintConfig withColor(Color color) {
156 0 : return TextPaintConfig(
157 0 : fontSize: fontSize,
158 : color: color,
159 0 : fontFamily: fontFamily,
160 0 : textDirection: textDirection,
161 : );
162 : }
163 :
164 : /// Creates a new [TextPaintConfig] changing only the [fontFamily].
165 : ///
166 : /// This does not change the original (as it's immutable).
167 1 : TextPaintConfig withFontFamily(String fontFamily) {
168 1 : return TextPaintConfig(
169 1 : fontSize: fontSize,
170 1 : color: color,
171 : fontFamily: fontFamily,
172 1 : textDirection: textDirection,
173 : );
174 : }
175 :
176 : /// Creates a new [TextPaintConfig] changing only the [textAlign].
177 : ///
178 : /// This does not change the original (as it's immutable).
179 0 : TextPaintConfig withTextAlign(TextAlign textAlign) {
180 0 : return TextPaintConfig(
181 0 : fontSize: fontSize,
182 0 : color: color,
183 0 : fontFamily: fontFamily,
184 0 : textDirection: textDirection,
185 : );
186 : }
187 :
188 : /// Creates a new [TextPaintConfig] changing only the [textDirection].
189 : ///
190 : /// This does not change the original (as it's immutable).
191 0 : TextPaintConfig withTextDirection(TextDirection textDirection) {
192 0 : return TextPaintConfig(
193 0 : fontSize: fontSize,
194 0 : color: color,
195 0 : fontFamily: fontFamily,
196 : textDirection: textDirection,
197 : );
198 : }
199 : }
200 :
201 : /// A Text Config contains all typographical information required to render texts; i.e., font size and color, family, etc.
202 : ///
203 : /// It does not hold information regarding the position of the text to be render neither the text itself (the string).
204 : /// To hold all those information, use the Text component.
205 : ///
206 : /// It is used by [TextComponent].
207 : class TextPaint extends TextRenderer<TextPaintConfig> {
208 : final MemoryCache<String, material.TextPainter> _textPainterCache =
209 : MemoryCache();
210 :
211 1 : TextPaint({
212 : TextPaintConfig config = const TextPaintConfig(),
213 1 : }) : super(config: config);
214 :
215 0 : @override
216 : void render(
217 : Canvas canvas,
218 : String text,
219 : Vector2 p, {
220 : Anchor anchor = Anchor.topLeft,
221 : }) {
222 0 : final tp = toTextPainter(text);
223 0 : final translatedPosition = anchor.translate(p, tp.size.toVector2());
224 0 : tp.paint(canvas, translatedPosition.toOffset());
225 : }
226 :
227 1 : @override
228 : double measureTextWidth(String text) {
229 2 : return toTextPainter(text).width;
230 : }
231 :
232 1 : @override
233 : double measureTextHeight(String text) {
234 2 : return toTextPainter(text).height;
235 : }
236 :
237 : /// Returns a [material.TextPainter] that allows for text rendering and size measuring.
238 : ///
239 : /// A [material.TextPainter] has three important properties: paint, width and height (or size).
240 : ///
241 : /// Example usage:
242 : ///
243 : /// const TextPaint config = TextPaint(fontSize: 48.0, fontFamily: 'Awesome Font');
244 : /// final tp = config.toTextPainter('Score: $score');
245 : /// tp.paint(c, Offset(size.width - p.width - 10, size.height - p.height - 10));
246 : ///
247 : /// However, you probably want to use the [render] method which already renders for you considering the anchor.
248 : /// That way, you don't need to perform the math for yourself.
249 1 : material.TextPainter toTextPainter(String text) {
250 2 : if (!_textPainterCache.containsKey(text)) {
251 1 : final style = material.TextStyle(
252 2 : color: config.color,
253 2 : fontSize: config.fontSize,
254 2 : fontFamily: config.fontFamily,
255 2 : height: config.lineHeight,
256 : );
257 1 : final span = material.TextSpan(
258 : style: style,
259 : text: text,
260 : );
261 1 : final tp = material.TextPainter(
262 : text: span,
263 2 : textDirection: config.textDirection,
264 : );
265 1 : tp.layout();
266 :
267 2 : _textPainterCache.setValue(text, tp);
268 : }
269 2 : return _textPainterCache.getValue(text)!;
270 : }
271 :
272 1 : @override
273 : TextPaint copyWith(
274 : TextPaintConfig Function(TextPaintConfig) transform,
275 : ) {
276 3 : return TextPaint(config: transform(config));
277 : }
278 : }
|