forked from flutter/engine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtext.dart
2282 lines (2085 loc) · 85.3 KB
/
text.dart
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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.9
part of dart.ui;
/// Whether to slant the glyphs in the font
enum FontStyle {
/// Use the upright glyphs
normal,
/// Use glyphs designed for slanting
italic,
}
/// The thickness of the glyphs used to draw the text
class FontWeight {
const FontWeight._(this.index);
/// The encoded integer value of this font weight.
final int index;
/// Thin, the least thick
static const FontWeight w100 = FontWeight._(0);
/// Extra-light
static const FontWeight w200 = FontWeight._(1);
/// Light
static const FontWeight w300 = FontWeight._(2);
/// Normal / regular / plain
static const FontWeight w400 = FontWeight._(3);
/// Medium
static const FontWeight w500 = FontWeight._(4);
/// Semi-bold
static const FontWeight w600 = FontWeight._(5);
/// Bold
static const FontWeight w700 = FontWeight._(6);
/// Extra-bold
static const FontWeight w800 = FontWeight._(7);
/// Black, the most thick
static const FontWeight w900 = FontWeight._(8);
/// The default font weight.
static const FontWeight normal = w400;
/// A commonly used font weight that is heavier than normal.
static const FontWeight bold = w700;
/// A list of all the font weights.
static const List<FontWeight> values = <FontWeight>[
w100, w200, w300, w400, w500, w600, w700, w800, w900
];
/// Linearly interpolates between two font weights.
///
/// Rather than using fractional weights, the interpolation rounds to the
/// nearest weight.
///
/// If both `a` and `b` are null, then this method will return null. Otherwise,
/// any null values for `a` or `b` are interpreted as equivalent to [normal]
/// (also known as [w400]).
///
/// The `t` argument represents position on the timeline, with 0.0 meaning
/// that the interpolation has not started, returning `a` (or something
/// equivalent to `a`), 1.0 meaning that the interpolation has finished,
/// returning `b` (or something equivalent to `b`), and values in between
/// meaning that the interpolation is at the relevant point on the timeline
/// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
/// 1.0, so negative values and values greater than 1.0 are valid (and can
/// easily be generated by curves such as [Curves.elasticInOut]). The result
/// is clamped to the range [w100]–[w900].
///
/// Values for `t` are usually obtained from an [Animation<double>], such as
/// an [AnimationController].
static FontWeight? lerp(FontWeight? a, FontWeight? b, double t) {
assert(t != null); // ignore: unnecessary_null_comparison
if (a == null && b == null)
return null;
return values[_lerpInt((a ?? normal).index, (b ?? normal).index, t).round().clamp(0, 8) as int];
}
@override
String toString() {
return const <int, String>{
0: 'FontWeight.w100',
1: 'FontWeight.w200',
2: 'FontWeight.w300',
3: 'FontWeight.w400',
4: 'FontWeight.w500',
5: 'FontWeight.w600',
6: 'FontWeight.w700',
7: 'FontWeight.w800',
8: 'FontWeight.w900',
}[index]!;
}
}
/// A feature tag and value that affect the selection of glyphs in a font.
///
/// {@tool sample}
///
/// This example shows usage of several OpenType font features, including
/// Small Caps (smcp), old-style figures, fractional ligatures and stylistic
/// sets.
///
/// ```dart
///class TypePage extends StatelessWidget {
/// // The Cardo, Milonga and Raleway Dots fonts can be downloaded from
/// // Google Fonts (https://www.google.com/fonts).
///
/// final titleStyle = TextStyle(
/// fontSize: 18,
/// fontFeatures: [FontFeature.enable('smcp')],
/// color: Colors.blueGrey[600],
/// );
///
/// @override
/// Widget build(BuildContext context) {
/// return Scaffold(
/// body: Center(
/// child: Column(
/// mainAxisAlignment: MainAxisAlignment.center,
/// children: <Widget>[
/// Spacer(flex: 5),
/// Text('regular numbers have their place:', style: titleStyle),
/// Text('The 1972 cup final was a 1-1 draw.',
/// style: TextStyle(
/// fontFamily: 'Cardo',
/// fontSize: 24,
/// )),
/// Spacer(),
/// Text('but old-style figures blend well with lower case:',
/// style: titleStyle),
/// Text('The 1972 cup final was a 1-1 draw.',
/// style: TextStyle(
/// fontFamily: 'Cardo',
/// fontSize: 24,
/// fontFeatures: [FontFeature.oldstyleFigures()])),
/// Spacer(),
/// Divider(),
/// Spacer(),
/// Text('fractions look better with a custom ligature:',
/// style: titleStyle),
/// Text('Add 1/2 tsp of flour and stir.',
/// style: TextStyle(
/// fontFamily: 'Milonga',
/// fontSize: 24,
/// fontFeatures: [FontFeature.enable('frac')])),
/// Spacer(),
/// Divider(),
/// Spacer(),
/// Text('multiple stylistic sets in one font:', style: titleStyle),
/// Text('Raleway Dots',
/// style: TextStyle(fontFamily: 'Raleway Dots', fontSize: 48)),
/// Text('Raleway Dots',
/// style: TextStyle(
/// fontFeatures: [FontFeature.stylisticSet(1)],
/// fontFamily: 'Raleway Dots',
/// fontSize: 48,
/// )),
/// Spacer(flex: 5),
/// ],
/// ),
/// ),
/// );
/// }
///}
/// ```
/// {@end-tool}
class FontFeature {
/// Creates a [FontFeature] object, which can be added to a [TextStyle] to
/// change how the engine selects glyphs when rendering text.
///
/// `feature` is the four-character tag that identifies the feature.
/// These tags are specified by font formats such as OpenType.
///
/// `value` is the value that the feature will be set to. The behavior
/// of the value depends on the specific feature. Many features are
/// flags whose value can be 1 (when enabled) or 0 (when disabled).
///
/// See <https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags>
// ignore: unnecessary_null_comparison
const FontFeature(this.feature, [ this.value = 1 ]) : assert(feature != null), assert(feature.length == 4), assert(value != null), assert(value >= 0);
/// Create a [FontFeature] object that enables the feature with the given tag.
const FontFeature.enable(String feature) : this(feature, 1);
/// Create a [FontFeature] object that disables the feature with the given tag.
const FontFeature.disable(String feature) : this(feature, 0);
/// Randomize the alternate forms used in text.
///
/// For example, this can be used with suitably-prepared handwriting fonts to
/// vary the forms used for each character, so that, for instance, the word
/// "cross-section" would be rendered with two different "c"s, two different "o"s,
/// and three different "s"s.
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#rand>
const FontFeature.randomize() : feature = 'rand', value = 1;
/// Select a stylistic set.
///
/// Fonts may have up to 20 stylistic sets, numbered 1 through 20.
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#ssxx>
factory FontFeature.stylisticSet(int value) {
assert(value >= 1);
assert(value <= 20);
return FontFeature('ss${value.toString().padLeft(2, "0")}');
}
/// Use the slashed zero.
///
/// Some fonts contain both a circular zero and a zero with a slash. This
/// enables the use of the latter form.
///
/// This is overridden by [FontFeature.oldstyleFigures].
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_uz#zero>
const FontFeature.slashedZero() : feature = 'zero', value = 1;
/// Use oldstyle figures.
///
/// Some fonts have variants of the figures (e.g. the digit 9) that, when
/// this feature is enabled, render with descenders under the baseline instead
/// of being entirely above the baseline.
///
/// This overrides [FontFeature.slashedZero].
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ko#onum>
const FontFeature.oldstyleFigures() : feature = 'onum', value = 1;
/// Use proportional (varying width) figures.
///
/// For fonts that have both proportional and tabular (monospace) figures,
/// this enables the proportional figures.
///
/// This is mutually exclusive with [FontFeature.tabularFigures].
///
/// The default behavior varies from font to font.
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#pnum>
const FontFeature.proportionalFigures() : feature = 'pnum', value = 1;
/// Use tabular (monospace) figures.
///
/// For fonts that have both proportional (varying width) and tabular figures,
/// this enables the tabular figures.
///
/// This is mutually exclusive with [FontFeature.proportionalFigures].
///
/// The default behavior varies from font to font.
///
/// See also:
///
/// * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#tnum>
const FontFeature.tabularFigures() : feature = 'tnum', value = 1;
/// The tag that identifies the effect of this feature. Must consist of 4
/// ASCII characters (typically lowercase letters).
///
/// See <https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags>
final String feature;
/// The value assigned to this feature.
///
/// Must be a positive integer. Many features are Boolean values that accept
/// values of either 0 (feature is disabled) or 1 (feature is enabled).
final int value;
static const int _kEncodedSize = 8;
void _encode(ByteData byteData) {
assert(feature.codeUnits.every((int c) => c >= 0x20 && c <= 0x7F));
for (int i = 0; i < 4; i++) {
byteData.setUint8(i, feature.codeUnitAt(i));
}
byteData.setInt32(4, value, _kFakeHostEndian);
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is FontFeature
&& other.feature == feature
&& other.value == value;
}
@override
int get hashCode => hashValues(feature, value);
@override
String toString() => 'FontFeature($feature, $value)';
}
/// Whether and how to align text horizontally.
// The order of this enum must match the order of the values in RenderStyleConstants.h's ETextAlign.
enum TextAlign {
/// Align the text on the left edge of the container.
left,
/// Align the text on the right edge of the container.
right,
/// Align the text in the center of the container.
center,
/// Stretch lines of text that end with a soft line break to fill the width of
/// the container.
///
/// Lines that end with hard line breaks are aligned towards the [start] edge.
justify,
/// Align the text on the leading edge of the container.
///
/// For left-to-right text ([TextDirection.ltr]), this is the left edge.
///
/// For right-to-left text ([TextDirection.rtl]), this is the right edge.
start,
/// Align the text on the trailing edge of the container.
///
/// For left-to-right text ([TextDirection.ltr]), this is the right edge.
///
/// For right-to-left text ([TextDirection.rtl]), this is the left edge.
end,
}
/// A horizontal line used for aligning text.
enum TextBaseline {
/// The horizontal line used to align the bottom of glyphs for alphabetic characters.
alphabetic,
/// The horizontal line used to align ideographic characters.
ideographic,
}
/// A linear decoration to draw near the text.
class TextDecoration {
const TextDecoration._(this._mask);
/// Creates a decoration that paints the union of all the given decorations.
factory TextDecoration.combine(List<TextDecoration> decorations) {
int mask = 0;
for (TextDecoration decoration in decorations)
mask |= decoration._mask;
return TextDecoration._(mask);
}
final int _mask;
/// Whether this decoration will paint at least as much decoration as the given decoration.
bool contains(TextDecoration other) {
return (_mask | other._mask) == _mask;
}
/// Do not draw a decoration
static const TextDecoration none = TextDecoration._(0x0);
/// Draw a line underneath each line of text
static const TextDecoration underline = TextDecoration._(0x1);
/// Draw a line above each line of text
static const TextDecoration overline = TextDecoration._(0x2);
/// Draw a line through each line of text
static const TextDecoration lineThrough = TextDecoration._(0x4);
@override
bool operator ==(dynamic other) {
return other is TextDecoration
&& other._mask == _mask;
}
@override
int get hashCode => _mask.hashCode;
@override
String toString() {
if (_mask == 0)
return 'TextDecoration.none';
final List<String> values = <String>[];
if (_mask & underline._mask != 0)
values.add('underline');
if (_mask & overline._mask != 0)
values.add('overline');
if (_mask & lineThrough._mask != 0)
values.add('lineThrough');
if (values.length == 1)
return 'TextDecoration.${values[0]}';
return 'TextDecoration.combine([${values.join(", ")}])';
}
}
/// The style in which to draw a text decoration
enum TextDecorationStyle {
/// Draw a solid line
solid,
/// Draw two lines
double,
/// Draw a dotted line
dotted,
/// Draw a dashed line
dashed,
/// Draw a sinusoidal line
wavy
}
/// {@template flutter.dart:ui.textHeightBehavior}
/// Defines how the paragraph will apply [TextStyle.height] to the ascent of the
/// first line and descent of the last line.
///
/// Each boolean value represents whether the [TextStyle.height] modifier will
/// be applied to the corresponding metric. By default, all properties are true,
/// and [TextStyle.height] is applied as normal. When set to false, the font's
/// default ascent will be used.
/// {@endtemplate}
class TextHeightBehavior {
/// Creates a new TextHeightBehavior object.
///
/// * applyHeightToFirstAscent: When true, the [TextStyle.height] modifier
/// will be applied to the ascent of the first line. When false, the font's
/// default ascent will be used.
/// * applyHeightToLastDescent: When true, the [TextStyle.height] modifier
/// will be applied to the descent of the last line. When false, the font's
/// default descent will be used.
///
/// All properties default to true (height modifications applied as normal).
const TextHeightBehavior({
this.applyHeightToFirstAscent = true,
this.applyHeightToLastDescent = true,
});
/// Creates a new TextHeightBehavior object from an encoded form.
///
/// See [encode] for the creation of the encoded form.
const TextHeightBehavior.fromEncoded(int encoded)
: applyHeightToFirstAscent = (encoded & 0x1) == 0,
applyHeightToLastDescent = (encoded & 0x2) == 0;
/// Whether to apply the [TextStyle.height] modifier to the ascent of the first
/// line in the paragraph.
///
/// When true, the [TextStyle.height] modifier will be applied to to the ascent
/// of the first line. When false, the font's default ascent will be used and
/// the [TextStyle.height] will have no effect on the ascent of the first line.
///
/// This property only has effect if a non-null [TextStyle.height] is specified.
///
/// Defaults to true (height modifications applied as normal).
final bool applyHeightToFirstAscent;
/// Whether to apply the [TextStyle.height] modifier to the descent of the last
/// line in the paragraph.
///
/// When true, the [TextStyle.height] modifier will be applied to to the descent
/// of the last line. When false, the font's default descent will be used and
/// the [TextStyle.height] will have no effect on the descent of the last line.
///
/// This property only has effect if a non-null [TextStyle.height] is specified.
///
/// Defaults to true (height modifications applied as normal).
final bool applyHeightToLastDescent;
/// Returns an encoded int representation of this object.
int encode() {
return (applyHeightToFirstAscent ? 0 : 1 << 0) | (applyHeightToLastDescent ? 0 : 1 << 1);
}
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType)
return false;
return other is TextHeightBehavior
&& other.applyHeightToFirstAscent == applyHeightToFirstAscent
&& other.applyHeightToLastDescent == applyHeightToLastDescent;
}
@override
int get hashCode {
return hashValues(
applyHeightToFirstAscent,
applyHeightToLastDescent,
);
}
@override
String toString() {
return 'TextHeightBehavior('
'applyHeightToFirstAscent: $applyHeightToFirstAscent, '
'applyHeightToLastDescent: $applyHeightToLastDescent'
')';
}
}
/// Determines if lists [a] and [b] are deep equivalent.
///
/// Returns true if the lists are both null, or if they are both non-null, have
/// the same length, and contain the same elements in the same order. Returns
/// false otherwise.
bool _listEquals<T>(List<T>? a, List<T>? b) {
if (a == null)
return b == null;
if (b == null || a.length != b.length)
return false;
for (int index = 0; index < a.length; index += 1) {
if (a[index] != b[index])
return false;
}
return true;
}
// This encoding must match the C++ version of ParagraphBuilder::pushStyle.
//
// The encoded array buffer has 8 elements.
//
// - Element 0: A bit field where the ith bit indicates whether the ith element
// has a non-null value. Bits 8 to 12 indicate whether |fontFamily|,
// |fontSize|, |letterSpacing|, |wordSpacing|, and |height| are non-null,
// respectively. Bit 0 is unused.
//
// - Element 1: The |color| in ARGB with 8 bits per channel.
//
// - Element 2: A bit field indicating which text decorations are present in
// the |textDecoration| list. The ith bit is set if there's a TextDecoration
// with enum index i in the list.
//
// - Element 3: The |decorationColor| in ARGB with 8 bits per channel.
//
// - Element 4: The bit field of the |decorationStyle|.
//
// - Element 5: The index of the |fontWeight|.
//
// - Element 6: The enum index of the |fontStyle|.
//
// - Element 7: The enum index of the |textBaseline|.
//
Int32List _encodeTextStyle(
Color? color,
TextDecoration? decoration,
Color? decorationColor,
TextDecorationStyle? decorationStyle,
double? decorationThickness,
FontWeight? fontWeight,
FontStyle? fontStyle,
TextBaseline? textBaseline,
String? fontFamily,
List<String>? fontFamilyFallback,
double? fontSize,
double? letterSpacing,
double? wordSpacing,
double? height,
Locale? locale,
Paint? background,
Paint? foreground,
List<Shadow>? shadows,
List<FontFeature>? fontFeatures,
) {
final Int32List result = Int32List(8);
if (color != null) {
result[0] |= 1 << 1;
result[1] = color.value;
}
if (decoration != null) {
result[0] |= 1 << 2;
result[2] = decoration._mask;
}
if (decorationColor != null) {
result[0] |= 1 << 3;
result[3] = decorationColor.value;
}
if (decorationStyle != null) {
result[0] |= 1 << 4;
result[4] = decorationStyle.index;
}
if (fontWeight != null) {
result[0] |= 1 << 5;
result[5] = fontWeight.index;
}
if (fontStyle != null) {
result[0] |= 1 << 6;
result[6] = fontStyle.index;
}
if (textBaseline != null) {
result[0] |= 1 << 7;
result[7] = textBaseline.index;
}
if (decorationThickness != null) {
result[0] |= 1 << 8;
}
if (fontFamily != null || (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty)) {
result[0] |= 1 << 9;
// Passed separately to native.
}
if (fontSize != null) {
result[0] |= 1 << 10;
// Passed separately to native.
}
if (letterSpacing != null) {
result[0] |= 1 << 11;
// Passed separately to native.
}
if (wordSpacing != null) {
result[0] |= 1 << 12;
// Passed separately to native.
}
if (height != null) {
result[0] |= 1 << 13;
// Passed separately to native.
}
if (locale != null) {
result[0] |= 1 << 14;
// Passed separately to native.
}
if (background != null) {
result[0] |= 1 << 15;
// Passed separately to native.
}
if (foreground != null) {
result[0] |= 1 << 16;
// Passed separately to native.
}
if (shadows != null) {
result[0] |= 1 << 17;
// Passed separately to native.
}
if (fontFeatures != null) {
result[0] |= 1 << 18;
// Passed separately to native.
}
return result;
}
/// An opaque object that determines the size, position, and rendering of text.
///
/// See also:
///
/// * [TextStyle](https://api.flutter.dev/flutter/painting/TextStyle-class.html), the class in the [painting] library.
///
class TextStyle {
/// Creates a new TextStyle object.
///
/// * `color`: The color to use when painting the text. If this is specified, `foreground` must be null.
/// * `decoration`: The decorations to paint near the text (e.g., an underline).
/// * `decorationColor`: The color in which to paint the text decorations.
/// * `decorationStyle`: The style in which to paint the text decorations (e.g., dashed).
/// * `decorationThickness`: The thickness of the decoration as a muliplier on the thickness specified by the font.
/// * `fontWeight`: The typeface thickness to use when painting the text (e.g., bold).
/// * `fontStyle`: The typeface variant to use when drawing the letters (e.g., italics).
/// * `fontFamily`: The name of the font to use when painting the text (e.g., Roboto). If a `fontFamilyFallback` is
/// provided and `fontFamily` is not, then the first font family in `fontFamilyFallback` will take the position of
/// the preferred font family. When a higher priority font cannot be found or does not contain a glyph, a lower
/// priority font will be used.
/// * `fontFamilyFallback`: An ordered list of the names of the fonts to fallback on when a glyph cannot
/// be found in a higher priority font. When the `fontFamily` is null, the first font family in this list
/// is used as the preferred font. Internally, the 'fontFamily` is concatenated to the front of this list.
/// When no font family is provided through 'fontFamilyFallback' (null or empty) or `fontFamily`, then the
/// platform default font will be used.
/// * `fontSize`: The size of glyphs (in logical pixels) to use when painting the text.
/// * `letterSpacing`: The amount of space (in logical pixels) to add between each letter.
/// * `wordSpacing`: The amount of space (in logical pixels) to add at each sequence of white-space (i.e. between each word).
/// * `textBaseline`: The common baseline that should be aligned between this text span and its parent text span, or, for the root text spans, with the line box.
/// * `height`: The height of this text span, as a multiplier of the font size. Omitting `height` will allow the line height
/// to take the height as defined by the font, which may not be exactly the height of the fontSize.
/// * `locale`: The locale used to select region-specific glyphs.
/// * `background`: The paint drawn as a background for the text.
/// * `foreground`: The paint used to draw the text. If this is specified, `color` must be null.
/// * `fontFeatures`: The font features that should be applied to the text.
TextStyle({
Color? color,
TextDecoration? decoration,
Color? decorationColor,
TextDecorationStyle? decorationStyle,
double? decorationThickness,
FontWeight? fontWeight,
FontStyle? fontStyle,
TextBaseline? textBaseline,
String? fontFamily,
List<String>? fontFamilyFallback,
double? fontSize,
double? letterSpacing,
double? wordSpacing,
double? height,
Locale? locale,
Paint? background,
Paint? foreground,
List<Shadow>? shadows,
List<FontFeature>? fontFeatures,
}) : assert(color == null || foreground == null,
'Cannot provide both a color and a foreground\n'
'The color argument is just a shorthand for "foreground: Paint()..color = color".'
),
_encoded = _encodeTextStyle(
color,
decoration,
decorationColor,
decorationStyle,
decorationThickness,
fontWeight,
fontStyle,
textBaseline,
fontFamily,
fontFamilyFallback,
fontSize,
letterSpacing,
wordSpacing,
height,
locale,
background,
foreground,
shadows,
fontFeatures,
),
_fontFamily = fontFamily ?? '',
_fontFamilyFallback = fontFamilyFallback,
_fontSize = fontSize,
_letterSpacing = letterSpacing,
_wordSpacing = wordSpacing,
_height = height,
_decorationThickness = decorationThickness,
_locale = locale,
_background = background,
_foreground = foreground,
_shadows = shadows,
_fontFeatures = fontFeatures;
final Int32List _encoded;
final String _fontFamily;
final List<String>? _fontFamilyFallback;
final double? _fontSize;
final double? _letterSpacing;
final double? _wordSpacing;
final double? _height;
final double? _decorationThickness;
final Locale? _locale;
final Paint? _background;
final Paint? _foreground;
final List<Shadow>? _shadows;
final List<FontFeature>? _fontFeatures;
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
return other is TextStyle
&& other._fontFamily == _fontFamily
&& other._fontSize == _fontSize
&& other._letterSpacing == _letterSpacing
&& other._wordSpacing == _wordSpacing
&& other._height == _height
&& other._decorationThickness == _decorationThickness
&& other._locale == _locale
&& other._background == _background
&& other._foreground == _foreground
&& _listEquals<int>(other._encoded, _encoded)
&& _listEquals<Shadow>(other._shadows, _shadows)
&& _listEquals<String>(other._fontFamilyFallback, _fontFamilyFallback)
&& _listEquals<FontFeature>(other._fontFeatures, _fontFeatures);
}
@override
int get hashCode => hashValues(hashList(_encoded), _fontFamily, _fontFamilyFallback, _fontSize, _letterSpacing, _wordSpacing, _height, _locale, _background, _foreground, hashList(_shadows), _decorationThickness, hashList(_fontFeatures));
@override
String toString() {
return 'TextStyle('
'color: ${ _encoded[0] & 0x00002 == 0x00002 ? Color(_encoded[1]) : "unspecified"}, '
'decoration: ${ _encoded[0] & 0x00004 == 0x00004 ? TextDecoration._(_encoded[2]) : "unspecified"}, '
'decorationColor: ${ _encoded[0] & 0x00008 == 0x00008 ? Color(_encoded[3]) : "unspecified"}, '
'decorationStyle: ${ _encoded[0] & 0x00010 == 0x00010 ? TextDecorationStyle.values[_encoded[4]] : "unspecified"}, '
// The decorationThickness is not in encoded order in order to keep it near the other decoration properties.
'decorationThickness: ${_encoded[0] & 0x00100 == 0x00100 ? _decorationThickness : "unspecified"}, '
'fontWeight: ${ _encoded[0] & 0x00020 == 0x00020 ? FontWeight.values[_encoded[5]] : "unspecified"}, '
'fontStyle: ${ _encoded[0] & 0x00040 == 0x00040 ? FontStyle.values[_encoded[6]] : "unspecified"}, '
'textBaseline: ${ _encoded[0] & 0x00080 == 0x00080 ? TextBaseline.values[_encoded[7]] : "unspecified"}, '
'fontFamily: ${ _encoded[0] & 0x00200 == 0x00200
&& _fontFamily != '' ? _fontFamily : "unspecified"}, '
'fontFamilyFallback: ${ _encoded[0] & 0x00200 == 0x00200
&& _fontFamilyFallback != null
&& _fontFamilyFallback!.isNotEmpty ? _fontFamilyFallback : "unspecified"}, '
'fontSize: ${ _encoded[0] & 0x00400 == 0x00400 ? _fontSize : "unspecified"}, '
'letterSpacing: ${ _encoded[0] & 0x00800 == 0x00800 ? "${_letterSpacing}x" : "unspecified"}, '
'wordSpacing: ${ _encoded[0] & 0x01000 == 0x01000 ? "${_wordSpacing}x" : "unspecified"}, '
'height: ${ _encoded[0] & 0x02000 == 0x02000 ? "${_height}x" : "unspecified"}, '
'locale: ${ _encoded[0] & 0x04000 == 0x04000 ? _locale : "unspecified"}, '
'background: ${ _encoded[0] & 0x08000 == 0x08000 ? _background : "unspecified"}, '
'foreground: ${ _encoded[0] & 0x10000 == 0x10000 ? _foreground : "unspecified"}, '
'shadows: ${ _encoded[0] & 0x20000 == 0x20000 ? _shadows : "unspecified"}, '
'fontFeatures: ${ _encoded[0] & 0x40000 == 0x40000 ? _fontFeatures : "unspecified"}'
')';
}
}
// This encoding must match the C++ version ParagraphBuilder::build.
//
// The encoded array buffer has 6 elements.
//
// - Element 0: A bit mask indicating which fields are non-null.
// Bit 0 is unused. Bits 1-n are set if the corresponding index in the
// encoded array is non-null. The remaining bits represent fields that
// are passed separately from the array.
//
// - Element 1: The enum index of the |textAlign|.
//
// - Element 2: The enum index of the |textDirection|.
//
// - Element 3: The index of the |fontWeight|.
//
// - Element 4: The enum index of the |fontStyle|.
//
// - Element 5: The value of |maxLines|.
//
// - Element 6: The encoded value of |textHeightBehavior|.
//
Int32List _encodeParagraphStyle(
TextAlign? textAlign,
TextDirection? textDirection,
int? maxLines,
String? fontFamily,
double? fontSize,
double? height,
TextHeightBehavior? textHeightBehavior,
FontWeight? fontWeight,
FontStyle? fontStyle,
StrutStyle? strutStyle,
String? ellipsis,
Locale? locale,
) {
final Int32List result = Int32List(7); // also update paragraph_builder.cc
if (textAlign != null) {
result[0] |= 1 << 1;
result[1] = textAlign.index;
}
if (textDirection != null) {
result[0] |= 1 << 2;
result[2] = textDirection.index;
}
if (fontWeight != null) {
result[0] |= 1 << 3;
result[3] = fontWeight.index;
}
if (fontStyle != null) {
result[0] |= 1 << 4;
result[4] = fontStyle.index;
}
if (maxLines != null) {
result[0] |= 1 << 5;
result[5] = maxLines;
}
if (textHeightBehavior != null) {
result[0] |= 1 << 6;
result[6] = textHeightBehavior.encode();
}
if (fontFamily != null) {
result[0] |= 1 << 7;
// Passed separately to native.
}
if (fontSize != null) {
result[0] |= 1 << 8;
// Passed separately to native.
}
if (height != null) {
result[0] |= 1 << 9;
// Passed separately to native.
}
if (strutStyle != null) {
result[0] |= 1 << 10;
// Passed separately to native.
}
if (ellipsis != null) {
result[0] |= 1 << 11;
// Passed separately to native.
}
if (locale != null) {
result[0] |= 1 << 12;
// Passed separately to native.
}
return result;
}
/// An opaque object that determines the configuration used by
/// [ParagraphBuilder] to position lines within a [Paragraph] of text.
class ParagraphStyle {
/// Creates a new ParagraphStyle object.
///
/// * `textAlign`: The alignment of the text within the lines of the
/// paragraph. If the last line is ellipsized (see `ellipsis` below), the
/// alignment is applied to that line after it has been truncated but before
/// the ellipsis has been added.
// See: https://github.com/flutter/flutter/issues/9819
///
/// * `textDirection`: The directionality of the text, left-to-right (e.g.
/// Norwegian) or right-to-left (e.g. Hebrew). This controls the overall
/// directionality of the paragraph, as well as the meaning of
/// [TextAlign.start] and [TextAlign.end] in the `textAlign` field.
///
/// * `maxLines`: The maximum number of lines painted. Lines beyond this
/// number are silently dropped. For example, if `maxLines` is 1, then only
/// one line is rendered. If `maxLines` is null, but `ellipsis` is not null,
/// then lines after the first one that overflows the width constraints are
/// dropped. The width constraints are those set in the
/// [ParagraphConstraints] object passed to the [Paragraph.layout] method.
///
/// * `fontFamily`: The name of the font family to apply when painting the text,
/// in the absence of a `textStyle` being attached to the span.
///
/// * `fontSize`: The fallback size of glyphs (in logical pixels) to
/// use when painting the text. This is used when there is no [TextStyle].
///
/// * `height`: The fallback height of the spans as a multiplier of the font
/// size. The fallback height is used when no height is provided through
/// [TextStyle.height]. Omitting `height` here and in [TextStyle] will allow
/// the line height to take the height as defined by the font, which may not
/// be exactly the height of the `fontSize`.
///
/// * `textHeightBehavior`: Specifies how the `height` multiplier is
/// applied to ascent of the first line and the descent of the last line.
///
/// * `fontWeight`: The typeface thickness to use when painting the text
/// (e.g., bold).
///
/// * `fontStyle`: The typeface variant to use when drawing the letters (e.g.,
/// italics).
///
/// * `strutStyle`: The properties of the strut. Strut defines a set of minimum
/// vertical line height related metrics and can be used to obtain more
/// advanced line spacing behavior.
///
/// * `ellipsis`: String used to ellipsize overflowing text. If `maxLines` is
/// not null, then the `ellipsis`, if any, is applied to the last rendered
/// line, if that line overflows the width constraints. If `maxLines` is
/// null, then the `ellipsis` is applied to the first line that overflows
/// the width constraints, and subsequent lines are dropped. The width
/// constraints are those set in the [ParagraphConstraints] object passed to
/// the [Paragraph.layout] method. The empty string and the null value are
/// considered equivalent and turn off this behavior.
///
/// * `locale`: The locale used to select region-specific glyphs.
ParagraphStyle({
TextAlign? textAlign,
TextDirection? textDirection,
int? maxLines,
String? fontFamily,
double? fontSize,
double? height,
TextHeightBehavior? textHeightBehavior,
FontWeight? fontWeight,
FontStyle? fontStyle,
StrutStyle? strutStyle,
String? ellipsis,
Locale? locale,
}) : _encoded = _encodeParagraphStyle(
textAlign,
textDirection,
maxLines,
fontFamily,
fontSize,
height,
textHeightBehavior,
fontWeight,
fontStyle,
strutStyle,
ellipsis,
locale,
),
_fontFamily = fontFamily,
_fontSize = fontSize,
_height = height,
_strutStyle = strutStyle,
_ellipsis = ellipsis,
_locale = locale;