1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui show ParagraphBuilder;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'basic_types.dart';
import 'text_painter.dart';
import 'text_span.dart';
import 'text_style.dart';
/// Mutable wrapper of an integer that can be passed by reference to track a
/// value across a recursive stack.
class Accumulator {
/// [Accumulator] may be initialized with a specified value, otherwise, it will
/// initialize to zero.
Accumulator([this._value = 0]);
/// The integer stored in this [Accumulator].
int get value => _value;
int _value;
/// Increases the [value] by the `addend`.
void increment(int addend) {
assert(addend >= 0);
_value += addend;
}
}
/// Called on each span as [InlineSpan.visitChildren] walks the [InlineSpan] tree.
///
/// Returns true when the walk should continue, and false to stop visiting further
/// [InlineSpan]s.
typedef InlineSpanVisitor = bool Function(InlineSpan span);
/// The textual and semantic label information for an [InlineSpan].
///
/// For [PlaceholderSpan]s, [InlineSpanSemanticsInformation.placeholder] is used by default.
///
/// See also:
/// * [InlineSpan.getSemanticsInformation]
@immutable
class InlineSpanSemanticsInformation {
/// Constructs an object that holds the text and sematnics label values of an
/// [InlineSpan].
///
/// The text parameter must not be null.
///
/// Use [InlineSpanSemanticsInformation.placeholder] instead of directly setting
/// [isPlaceholder].
const InlineSpanSemanticsInformation(
this.text, {
this.isPlaceholder = false,
this.semanticsLabel,
this.recognizer
}) : assert(text != null),
assert(isPlaceholder != null),
assert(isPlaceholder == false || (text == '\uFFFC' && semanticsLabel == null && recognizer == null)),
requiresOwnNode = isPlaceholder || recognizer != null;
/// The text info for a [PlaceholderSpan].
static const InlineSpanSemanticsInformation placeholder = InlineSpanSemanticsInformation('\uFFFC', isPlaceholder: true);
/// The text value, if any. For [PlaceholderSpan]s, this will be the unicode
/// placeholder value.
final String text;
/// The semanticsLabel, if any.
final String semanticsLabel;
/// The gesture recognizer, if any, for this span.
final GestureRecognizer recognizer;
/// Whether this is for a placeholder span.
final bool isPlaceholder;
/// True if this configuration should get its own semantics node.
///
/// This will be the case of the [recognizer] is not null, of if
/// [isPlaceholder] is true.
final bool requiresOwnNode;
@override
bool operator ==(dynamic other) {
if (other is! InlineSpanSemanticsInformation) {
return false;
}
return other.text == text && other.semanticsLabel == semanticsLabel && other.recognizer == recognizer && other.isPlaceholder == isPlaceholder;
}
@override
int get hashCode => hashValues(text, semanticsLabel, recognizer, isPlaceholder);
@override
String toString() => '$runtimeType{text: $text, semanticsLabel: $semanticsLabel, recognizer: $recognizer}';
}
/// An immutable span of inline content which forms part of a paragraph.
///
/// * The subclass [TextSpan] specifies text and may contain child [InlineSpan]s.
/// * The subclass [PlaceholderSpan] represents a placeholder that may be
/// filled with non-text content. [PlaceholderSpan] itself defines a
/// [ui.PlaceholderAlignemnt] and a [TextBaseline]. To be useful,
/// [PlaceholderSpan] must be extended to define content. An instance of
/// this is the [WidgetSpan] class in the widgets library.
/// * The subclass [WidgetSpan] specifies embedded inline widgets.
///
/// {@tool sample}
///
/// This example shows a tree of [InlineSpan]s that make a query asking for a
/// name with a [TextField] embedded inline.
///
/// ```dart
/// Text.rich(
/// TextSpan(
/// text: 'My name is ',
/// style: TextStyle(color: Colors.black),
/// children: <InlineSpan>[
/// WidgetSpan(
/// alignment: PlaceholderAlignment.baseline,
/// baseline: TextBaseline.alphabetic,
/// child: ConstrainedBox(
/// constraints: BoxConstraints(maxWidth: 100),
/// child: TextField(),
/// )
/// ),
/// TextSpan(
/// text: '.',
/// ),
/// ],
/// ),
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Text], a widget for showing uniformly-styled text.
/// * [RichText], a widget for finer control of text rendering.
/// * [TextPainter], a class for painting [InlineSpan] objects on a [Canvas].
@immutable
abstract class InlineSpan extends DiagnosticableTree {
/// Creates an [InlineSpan] with the given values.
const InlineSpan({
this.style,
});
/// The [TextStyle] to apply to this span.
///
/// The [style] is also applied to any child spans when this is an instance
/// of [TextSpan].
final TextStyle style;
// TODO(garyq): Remove the deprecated visitTextSpan, text, and children.
/// Returns the text associated with this span if this is an instance of [TextSpan],
/// otherwise returns null.
@Deprecated('InlineSpan does not innately have text. Use TextSpan.text instead.')
String get text => null;
// TODO(garyq): Remove the deprecated visitTextSpan, text, and children.
/// Returns the [InlineSpan] children list associated with this span if this is an
/// instance of [TextSpan], otherwise returns null.
@Deprecated('InlineSpan does not innately have children. Use TextSpan.children instead.')
List<InlineSpan> get children => null;
/// Returns the [GestureRecognizer] associated with this span if this is an
/// instance of [TextSpan], otherwise returns null.
@Deprecated('InlineSpan does not innately have a recognizer. Use TextSpan.recognizer instead.')
GestureRecognizer get recognizer => null;
/// Apply the properties of this object to the given [ParagraphBuilder], from
/// which a [Paragraph] can be obtained.
///
/// The `textScaleFactor` parameter specifies a scale that the text and
/// placeholders will be scaled by. The scaling is performed before layout,
/// so the text will be laid out with the scaled glyphs and placeholders.
///
/// The `dimensions` parameter specifies the sizes of the placeholders.
/// Each [PlaceholderSpan] must be paired with a [PlaceholderDimensions]
/// in the same order as defined in the [InlineSpan] tree.
///
/// [Paragraph] objects can be drawn on [Canvas] objects.
void build(ui.ParagraphBuilder builder, { double textScaleFactor = 1.0, List<PlaceholderDimensions> dimensions });
// TODO(garyq): Remove the deprecated visitTextSpan, text, and children.
/// Walks this [TextSpan] and any descendants in pre-order and calls `visitor`
/// for each span that has content.
///
/// When `visitor` returns true, the walk will continue. When `visitor` returns
/// false, then the walk will end.
@Deprecated('Use visitChildren instead')
bool visitTextSpan(bool visitor(TextSpan span));
/// Walks this [InlineSpan] and any descendants in pre-order and calls `visitor`
/// for each span that has content.
///
/// When `visitor` returns true, the walk will continue. When `visitor` returns
/// false, then the walk will end.
bool visitChildren(InlineSpanVisitor visitor);
/// Returns the [InlineSpan] that contains the given position in the text.
InlineSpan getSpanForPosition(TextPosition position) {
assert(debugAssertIsValid());
final Accumulator offset = Accumulator();
InlineSpan result;
visitChildren((InlineSpan span) {
result = span.getSpanForPositionVisitor(position, offset);
return result == null;
});
return result;
}
/// Performs the check at each [InlineSpan] for if the `position` falls within the range
/// of the span and returns the span if it does.
///
/// The `offset` parameter tracks the current index offset in the text buffer formed
/// if the contents of the [InlineSpan] tree were concatenated together starting
/// from the root [InlineSpan].
///
/// This method should not be directly called. Use [getSpanForPosition] instead.
@protected
InlineSpan getSpanForPositionVisitor(TextPosition position, Accumulator offset);
/// Flattens the [InlineSpan] tree into a single string.
///
/// Styles are not honored in this process. If `includeSemanticsLabels` is
/// true, then the text returned will include the [TextSpan.semanticsLabel]s
/// instead of the text contents for [TextSpan]s.
///
/// When `includePlaceholders` is true, [PlaceholderSpan]s in the tree will be
/// represented as a 0xFFFC 'object replacement character'.
String toPlainText({bool includeSemanticsLabels = true, bool includePlaceholders = true}) {
final StringBuffer buffer = StringBuffer();
computeToPlainText(buffer, includeSemanticsLabels: includeSemanticsLabels, includePlaceholders: includePlaceholders);
return buffer.toString();
}
/// Flattens the [InlineSpan] tree to a list of
/// [InlineSpanSemanticsInformation] objects.
///
/// [PlaceholderSpan]s in the tree will be represented with a
/// [InlineSpanSemanticsInformation.placeholder] value.
List<InlineSpanSemanticsInformation> getSemanticsInformation() {
final List<InlineSpanSemanticsInformation> collector = <InlineSpanSemanticsInformation>[];
computeSemanticsInformation(collector);
return collector;
}
/// Walks the [InlineSpan] tree and accumulates a list of
/// [InlineSpanSemanticsInformation] objects.
///
/// This method should not be directly called. Use
/// [getSemanticsInformation] instead.
///
/// [PlaceholderSpan]s in the tree will be represented with a
/// [InlineSpanSemanticsInformation.placeholder] value.
@protected
void computeSemanticsInformation(List<InlineSpanSemanticsInformation> collector);
/// Walks the [InlineSpan] tree and writes the plain text representation to `buffer`.
///
/// This method should not be directly called. Use [toPlainText] instead.
///
/// Styles are not honored in this process. If `includeSemanticsLabels` is
/// true, then the text returned will include the [TextSpan.semanticsLabel]s
/// instead of the text contents for [TextSpan]s.
///
/// When `includePlaceholders` is true, [PlaceholderSpan]s in the tree will be
/// represented as a 0xFFFC 'object replacement character'.
///
/// The plain-text representation of this [InlineSpan] is written into the `buffer`.
/// This method will then recursively call [computeToPlainText] on its childen
/// [InlineSpan]s if available.
@protected
void computeToPlainText(StringBuffer buffer, {bool includeSemanticsLabels = true, bool includePlaceholders = true});
/// Returns the UTF-16 code unit at the given `index` in the flattened string.
///
/// This only accounts for the [TextSpan.text] values and ignores [PlaceholderSpans].
///
/// Returns null if the `index` is out of bounds.
int codeUnitAt(int index) {
if (index < 0)
return null;
final Accumulator offset = Accumulator();
int result;
visitChildren((InlineSpan span) {
result = span.codeUnitAtVisitor(index, offset);
return result == null;
});
return result;
}
/// Performs the check at each [InlineSpan] for if the `index` falls within the range
/// of the span and returns the corresponding code unit. Returns null otherwise.
///
/// The `offset` parameter tracks the current index offset in the text buffer formed
/// if the contents of the [InlineSpan] tree were concatenated together starting
/// from the root [InlineSpan].
///
/// This method should not be directly called. Use [codeUnitAt] instead.
@protected
int codeUnitAtVisitor(int index, Accumulator offset);
/// Populates the `semanticsOffsets` and `semanticsElements` with the appropriate data
/// to be able to construct a [SemanticsNode].
///
/// If applicable, the beginning and end text offset are added to [semanticsOffsets].
/// [PlaceholderSpan]s have a text length of 1, which corresponds to the object
/// replacement character (0xFFFC) that is inserted to represent it.
///
/// Any [GestureRecognizer]s are added to `semanticsElements`. Null is added to
/// `semanticsElements` for [PlaceholderSpan]s.
@Deprecated('Implement computeSemanticsInformation instead.')
void describeSemantics(Accumulator offset, List<int> semanticsOffsets, List<dynamic> semanticsElements);
/// In checked mode, throws an exception if the object is not in a
/// valid configuration. Otherwise, returns true.
///
/// This is intended to be used as follows:
///
/// ```dart
/// assert(myInlineSpan.debugAssertIsValid());
/// ```
bool debugAssertIsValid() => true;
/// Describe the difference between this span and another, in terms of
/// how much damage it will make to the rendering. The comparison is deep.
///
/// Comparing [InlineSpan] objects of different types, for example, comparing
/// a [TextSpan] to a [WidgetSpan], always results in [RenderComparison.layout].
///
/// See also:
///
/// * [TextStyle.compareTo], which does the same thing for [TextStyle]s.
RenderComparison compareTo(InlineSpan other);
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
final InlineSpan typedOther = other;
return typedOther.style == style;
}
@override
int get hashCode => style.hashCode;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace;
if (style != null) {
style.debugFillProperties(properties);
}
}
}