Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit f1505f5

Browse files
Allow unsetting TextStyle.height (#52940)
Introduces a sentinel value `kTextHeightNone` for `ui.TextStyle.height` which can be used to "unset" the current `TextStyle.height` (and for consistency, it applies to `StructStyle.height` and `ParagraphStyle.height` too). Documentation of `TextStyle.height` can be found [here](https://main-api.flutter.dev/flutter/painting/TextStyle/height.html) (the one from `painting` library not `dart:ui`). part of flutter/flutter#58765: currently `TextStyle.height` uses `null` as the sentinel value for "no height multiplier specified, use the font height", which has conflicting semantics: it means the height multiplier is not set (so the span height is determined by font metrics) but in reality it also means the height should inherit from its parent span (or in `copyWith` context, it means do not override the height). The new sentinel value `kTextHeightNone` is currently set to `0.0`. This is because skparagraph internally uses 0 for "no height multiplier", so using 0 should minimize the behavior change: https://github.com/google/skia/blob/62f369c759947272dfdd2d8f060afadbcc361e79/modules/skparagraph/src/Run.cpp#L65-L67 This MAY still change the current behavior: for consistency setting `StructStyle.height` / `ParagraphStyle.height` to the sentinel value also unsets the height multiplier which may not be the current behavior. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 768c90e commit f1505f5

File tree

11 files changed

+161
-55
lines changed

11 files changed

+161
-55
lines changed

lib/ui/text.dart

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
// found in the LICENSE file.
44
part of dart.ui;
55

6+
/// A [TextStyle.height] value that indicates the text span should take
7+
/// the height defined by the font, which may not be exactly the height of
8+
/// [TextStyle.fontSize].
9+
// To change the sentinel value, search for "kTextHeightNone" in the source code.
10+
const double kTextHeightNone = 0.0;
11+
612
/// Whether to use the italic type variation of glyphs in the font.
713
///
814
/// Some modern fonts allow this to be selected in a more fine-grained manner.
@@ -1687,10 +1693,10 @@ class TextStyle {
16871693
/// * `letterSpacing`: The amount of space (in logical pixels) to add between each letter.
16881694
/// * `wordSpacing`: The amount of space (in logical pixels) to add at each sequence of white-space (i.e. between each word).
16891695
/// * `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.
1690-
/// * `height`: The height of this text span, as a multiplier of the font size. Omitting `height` will allow the line height
1696+
/// * `height`: The height of this text span, as a multiplier of the font size. Setting the `height` to `kTextHeightNone` will allow the line height
16911697
/// to take the height as defined by the font, which may not be exactly the height of the fontSize.
1692-
/// * `leadingDistribution`: When `height` is specified, how the extra vertical space should be distributed over and under the text. Defaults
1693-
/// to the paragraph's [TextHeightBehavior] if left unspecified.
1698+
/// * `leadingDistribution`: When `height` is set to a non-null that is not `kTextHeightNone`, how the extra vertical space should be distributed over and under the text.
1699+
/// Defaults to the paragraph's [TextHeightBehavior] if left unspecified.
16941700
/// * `locale`: The locale used to select region-specific glyphs.
16951701
/// * `background`: The paint drawn as a background for the text.
16961702
/// * `foreground`: The paint used to draw the text. If this is specified, `color` must be null.
@@ -1825,6 +1831,7 @@ class TextStyle {
18251831
@override
18261832
String toString() {
18271833
final List<String>? fontFamilyFallback = _fontFamilyFallback;
1834+
final String heightText = _encoded[0] & 0x02000 == 0x02000 ? (_height == kTextHeightNone ? 'kTextHeightNone' : '${_height}x') : 'unspecified';
18281835
return 'TextStyle('
18291836
'color: ${ _encoded[0] & 0x00002 == 0x00002 ? Color(_encoded[1]) : "unspecified"}, '
18301837
'decoration: ${ _encoded[0] & 0x00004 == 0x00004 ? TextDecoration._(_encoded[2]) : "unspecified"}, '
@@ -1843,7 +1850,7 @@ class TextStyle {
18431850
'fontSize: ${ _encoded[0] & 0x00400 == 0x00400 ? _fontSize : "unspecified"}, '
18441851
'letterSpacing: ${ _encoded[0] & 0x00800 == 0x00800 ? "${_letterSpacing}x" : "unspecified"}, '
18451852
'wordSpacing: ${ _encoded[0] & 0x01000 == 0x01000 ? "${_wordSpacing}x" : "unspecified"}, '
1846-
'height: ${ _encoded[0] & 0x02000 == 0x02000 ? "${_height}x" : "unspecified"}, '
1853+
'height: $heightText, '
18471854
'leadingDistribution: ${_leadingDistribution ?? "unspecified"}, '
18481855
'locale: ${ _encoded[0] & 0x04000 == 0x04000 ? _locale : "unspecified"}, '
18491856
'background: ${ _encoded[0] & 0x08000 == 0x08000 ? _background : "unspecified"}, '
@@ -1923,7 +1930,9 @@ Int32List _encodeParagraphStyle(
19231930
result[0] |= 1 << 8;
19241931
// Passed separately to native.
19251932
}
1926-
if (height != null) {
1933+
// Paragraph styles are unique in a paragraph, there is no inheriting so
1934+
// height == null and height == kTextHeightNone are semantically equivalent.
1935+
if (height != null && height != kTextHeightNone) {
19271936
result[0] |= 1 << 9;
19281937
// Passed separately to native.
19291938
}
@@ -1973,9 +1982,10 @@ class ParagraphStyle {
19731982
///
19741983
/// * `height`: The fallback height of the spans as a multiplier of the font
19751984
/// size. The fallback height is used when no height is provided through
1976-
/// [TextStyle.height]. Omitting `height` here and in [TextStyle] will allow
1977-
/// the line height to take the height as defined by the font, which may not
1978-
/// be exactly the height of the `fontSize`.
1985+
/// [TextStyle.height]. Omitting `height` here (or setting it to
1986+
/// [kTextHeightNone]) and in [TextStyle] will allow the line height to take
1987+
/// the height as defined by the font, which may not be exactly the height of
1988+
/// the `fontSize`.
19791989
///
19801990
/// * `textHeightBehavior`: Specifies how the `height` multiplier is
19811991
/// applied to ascent of the first line and the descent of the last line.
@@ -2110,9 +2120,12 @@ ByteData _encodeStrut(
21102120
FontWeight? fontWeight,
21112121
FontStyle? fontStyle,
21122122
bool? forceStrutHeight) {
2123+
// Strut styles are unique in a paragraph, there is no inheriting so
2124+
// height == null and height == kTextHeightNone are semantically equivalent.
2125+
final bool hasHeightOverride = height != null && height != kTextHeightNone;
21132126
if (fontFamily == null &&
21142127
fontSize == null &&
2115-
height == null &&
2128+
!hasHeightOverride &&
21162129
leadingDistribution == null &&
21172130
leading == null &&
21182131
fontWeight == null &&
@@ -2146,7 +2159,7 @@ ByteData _encodeStrut(
21462159
data.setFloat32(byteCount, fontSize, _kFakeHostEndian);
21472160
byteCount += 4;
21482161
}
2149-
if (height != null) {
2162+
if (hasHeightOverride) {
21502163
bitmask |= 1 << 5;
21512164
data.setFloat32(byteCount, height, _kFakeHostEndian);
21522165
byteCount += 4;
@@ -2186,12 +2199,13 @@ class StrutStyle {
21862199
/// * `height`: The minimum height of the line boxes, as a multiplier of the
21872200
/// font size. The lines of the paragraph will be at least
21882201
/// `(height + leading) * fontSize` tall when `fontSize` is not null. Omitting
2189-
/// `height` will allow the minimum line height to take the height as defined
2190-
/// by the font, which may not be exactly the height of the `fontSize`. When
2191-
/// `fontSize` is null, there is no minimum line height. Tall glyphs due to
2192-
/// baseline alignment or large [TextStyle.fontSize] may cause the actual line
2193-
/// height after layout to be taller than specified here. The `fontSize` must
2194-
/// be provided for this property to take effect.
2202+
/// `height` (or setting it to [kTextHeightNone]) will allow the minimum line
2203+
/// height to take the height as defined by the font, which may not be exactly
2204+
/// the height of the `fontSize`. When `fontSize` is null, there is no minimum
2205+
/// line height. Tall glyphs due to baseline alignment or large
2206+
/// [TextStyle.fontSize] may cause the actual line height after layout to be
2207+
/// taller than specified here. The `fontSize` must be provided for
2208+
/// this property to take effect.
21952209
///
21962210
/// * `leading`: The minimum amount of leading between lines as a multiple of
21972211
/// the font size. `fontSize` must be provided for this property to take

lib/ui/text/paragraph_builder.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
namespace flutter {
3131
namespace {
3232

33+
const double kTextHeightNone = 0.0;
34+
3335
// TextStyle
3436

3537
const int kTSLeadingDistributionIndex = 0;
@@ -445,7 +447,7 @@ void ParagraphBuilder::pushStyle(const tonic::Int32List& encoded,
445447

446448
if (mask & kTSHeightMask) {
447449
style.height = height;
448-
style.has_height_override = true;
450+
style.has_height_override = style.height != kTextHeightNone;
449451
}
450452

451453
if (mask & kTSLocaleMask) {

lib/web_ui/lib/src/engine/canvaskit/text.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class CkParagraphStyle implements ui.ParagraphStyle {
3939
maxLines,
4040
_computeEffectiveFontFamily(fontFamily),
4141
fontSize,
42-
height,
42+
height == ui.kTextHeightNone ? null : height,
4343
textHeightBehavior,
4444
fontWeight,
4545
fontStyle,
@@ -55,7 +55,7 @@ class CkParagraphStyle implements ui.ParagraphStyle {
5555
_originalFontFamily = fontFamily,
5656
_effectiveFontFamily = _computeEffectiveFontFamily(fontFamily),
5757
_fontSize = fontSize,
58-
_height = height,
58+
_height = height == ui.kTextHeightNone ? null : height,
5959
_textHeightBehavior = textHeightBehavior,
6060
_strutStyle = strutStyle,
6161
_ellipsis = ellipsis,
@@ -413,6 +413,9 @@ class CkTextStyle implements ui.TextStyle {
413413
/// The values in this text style are used unless [other] specifically
414414
/// overrides it.
415415
CkTextStyle mergeWith(CkTextStyle other) {
416+
final double? textHeight = other.height == ui.kTextHeightNone
417+
? null
418+
: (other.height ?? height);
416419
return CkTextStyle._(
417420
color: other.color ?? color,
418421
decoration: other.decoration ?? decoration,
@@ -429,7 +432,7 @@ class CkTextStyle implements ui.TextStyle {
429432
fontSize: other.fontSize ?? fontSize,
430433
letterSpacing: other.letterSpacing ?? letterSpacing,
431434
wordSpacing: other.wordSpacing ?? wordSpacing,
432-
height: other.height ?? height,
435+
height: textHeight,
433436
leadingDistribution: other.leadingDistribution ?? leadingDistribution,
434437
locale: other.locale ?? locale,
435438
background: other.background ?? background,
@@ -699,7 +702,7 @@ class CkStrutStyle implements ui.StrutStyle {
699702
}) : _fontFamily = _computeEffectiveFontFamily(fontFamily),
700703
_fontFamilyFallback = ui_web.debugEmulateFlutterTesterEnvironment ? null : fontFamilyFallback,
701704
_fontSize = fontSize,
702-
_height = height,
705+
_height = height == ui.kTextHeightNone ? null : height,
703706
_leadingDistribution = leadingDistribution,
704707
_leading = leading,
705708
_fontWeight = fontWeight,

lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ final class SkwasmStrutStyle extends SkwasmObjectWrapper<RawStrutStyle> implemen
594594
if (fontSize != null) {
595595
strutStyleSetFontSize(handle, fontSize);
596596
}
597-
if (height != null) {
597+
if (height != null && height != ui.kTextHeightNone) {
598598
strutStyleSetHeight(handle, height);
599599
}
600600
if (leadingDistribution != null) {
@@ -734,7 +734,7 @@ class SkwasmParagraphStyle extends SkwasmObjectWrapper<RawParagraphStyle> implem
734734
if (maxLines != null) {
735735
paragraphStyleSetMaxLines(handle, maxLines);
736736
}
737-
if (height != null) {
737+
if (height != null && height != ui.kTextHeightNone) {
738738
paragraphStyleSetHeight(handle, height);
739739
}
740740
if (textHeightBehavior != null) {

lib/web_ui/lib/src/engine/text/canvas_paragraph.dart

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -401,33 +401,29 @@ abstract class StyleNode {
401401
/// The resolved text style is equivalent to the entire ascendent chain of
402402
/// parent style nodes.
403403
EngineTextStyle resolveStyle() {
404-
final EngineTextStyle? style = _cachedStyle;
405-
if (style == null) {
406-
return _cachedStyle ??= EngineTextStyle(
407-
color: _color,
408-
decoration: _decoration,
409-
decorationColor: _decorationColor,
410-
decorationStyle: _decorationStyle,
411-
decorationThickness: _decorationThickness,
412-
fontWeight: _fontWeight,
413-
fontStyle: _fontStyle,
414-
textBaseline: _textBaseline,
415-
fontFamily: _fontFamily,
416-
fontFamilyFallback: _fontFamilyFallback,
417-
fontFeatures: _fontFeatures,
418-
fontVariations: _fontVariations,
419-
fontSize: _fontSize,
420-
letterSpacing: _letterSpacing,
421-
wordSpacing: _wordSpacing,
422-
height: _height,
423-
leadingDistribution: _leadingDistribution,
424-
locale: _locale,
425-
background: _background,
426-
foreground: _foreground,
427-
shadows: _shadows,
428-
);
429-
}
430-
return style;
404+
return _cachedStyle ??= EngineTextStyle(
405+
color: _color,
406+
decoration: _decoration,
407+
decorationColor: _decorationColor,
408+
decorationStyle: _decorationStyle,
409+
decorationThickness: _decorationThickness,
410+
fontWeight: _fontWeight,
411+
fontStyle: _fontStyle,
412+
textBaseline: _textBaseline,
413+
fontFamily: _fontFamily,
414+
fontFamilyFallback: _fontFamilyFallback,
415+
fontFeatures: _fontFeatures,
416+
fontVariations: _fontVariations,
417+
fontSize: _fontSize,
418+
letterSpacing: _letterSpacing,
419+
wordSpacing: _wordSpacing,
420+
height: _height,
421+
leadingDistribution: _leadingDistribution,
422+
locale: _locale,
423+
background: _background,
424+
foreground: _foreground,
425+
shadows: _shadows,
426+
);
431427
}
432428

433429
ui.Color? get _color;
@@ -510,7 +506,9 @@ class ChildStyleNode extends StyleNode {
510506
double? get _wordSpacing => style.wordSpacing ?? parent._wordSpacing;
511507

512508
@override
513-
double? get _height => style.height ?? parent._height;
509+
double? get _height {
510+
return style.height == ui.kTextHeightNone ? null : (style.height ?? parent._height);
511+
}
514512

515513
@override
516514
ui.TextLeadingDistribution? get _leadingDistribution => style.leadingDistribution ?? parent._leadingDistribution;

lib/web_ui/lib/src/engine/text/paragraph.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle {
435435
this.maxLines,
436436
this.fontFamily,
437437
this.fontSize,
438-
this.height,
438+
double? height,
439439
ui.TextHeightBehavior? textHeightBehavior,
440440
this.fontWeight,
441441
this.fontStyle,
@@ -444,7 +444,8 @@ class EngineParagraphStyle implements ui.ParagraphStyle {
444444
this.locale,
445445
}) : _textHeightBehavior = textHeightBehavior,
446446
// TODO(mdebbar): add support for strut style., b/128317744
447-
_strutStyle = strutStyle as EngineStrutStyle?;
447+
_strutStyle = strutStyle as EngineStrutStyle?,
448+
height = height == ui.kTextHeightNone ? null : height;
448449

449450
final ui.TextAlign? textAlign;
450451
final ui.TextDirection? textDirection;
@@ -808,7 +809,7 @@ class EngineStrutStyle implements ui.StrutStyle {
808809
}) : _fontFamily = fontFamily,
809810
_fontFamilyFallback = fontFamilyFallback,
810811
_fontSize = fontSize,
811-
_height = height,
812+
_height = height == ui.kTextHeightNone ? null : height,
812813
_leadingDistribution = leadingDistribution,
813814
_leading = leading,
814815
_fontWeight = fontWeight,

lib/web_ui/lib/text.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
part of ui;
66

7+
// This constant must be consistent with `kTextHeightNone` defined in
8+
// flutter/lib/ui/text.dart.
9+
// To change the sentinel value, search for "kTextHeightNone" in the source code.
10+
const double kTextHeightNone = 0.0;
11+
712
enum FontStyle {
813
normal,
914
italic,

lib/web_ui/skwasm/text/text_style.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include "../wrappers.h"
77
#include "third_party/skia/modules/skparagraph/include/Paragraph.h"
88

9+
const double kTextHeightNone = 0.0;
10+
911
using namespace skia::textlayout;
1012
using namespace Skwasm;
1113

@@ -96,7 +98,7 @@ SKWASM_EXPORT void textStyle_setWordSpacing(TextStyle* style,
9698

9799
SKWASM_EXPORT void textStyle_setHeight(TextStyle* style, SkScalar height) {
98100
style->setHeight(height);
99-
style->setHeightOverride(true);
101+
style->setHeightOverride(height != kTextHeightNone);
100102
}
101103

102104
SKWASM_EXPORT void textStyle_setHalfLeading(TextStyle* style,

lib/web_ui/test/ui/paragraph_builder_test.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:test/test.dart';
77
import 'package:ui/ui.dart';
88

99
import '../common/test_initialization.dart';
10+
import 'utils.dart';
1011

1112
void main() {
1213
internalBootstrapBrowserTest(() => testMain);
@@ -81,4 +82,46 @@ Future<void> testMain() async {
8182
returnsNormally,
8283
);
8384
});
85+
86+
test('kTextHeightNone unsets the height multiplier', () {
87+
const double fontSize = 10;
88+
const String text = 'A';
89+
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(fontSize: fontSize, height: 10));
90+
builder.pushStyle(TextStyle(height: kTextHeightNone));
91+
builder.addText(text);
92+
final Paragraph paragraph = builder.build()
93+
..layout(const ParagraphConstraints(width: 1000));
94+
// The height should be much smaller than fontSize * 10.
95+
expect(paragraph.height, lessThan(2 * fontSize));
96+
});
97+
98+
test('kTextHeightNone ParagraphStyle', () {
99+
const double fontSize = 10;
100+
final ParagraphBuilder builder = ParagraphBuilder(
101+
ParagraphStyle(fontSize: fontSize, height: kTextHeightNone, fontFamily: 'FlutterTest'),
102+
);
103+
builder.addText('A');
104+
final Paragraph paragraph = builder.build()
105+
..layout(const ParagraphConstraints(width: 1000));
106+
// The height should be much smaller than fontSize * 10.
107+
expect(paragraph.height, lessThan(2 * fontSize));
108+
});
109+
110+
test('kTextHeightNone StrutStyle', () {
111+
const double fontSize = 10;
112+
final ParagraphBuilder builder = ParagraphBuilder(
113+
ParagraphStyle(
114+
fontSize: 100,
115+
fontFamily: 'FlutterTest',
116+
strutStyle: StrutStyle(forceStrutHeight: true, height: kTextHeightNone, fontSize: fontSize),
117+
),
118+
);
119+
builder.addText('A');
120+
final Paragraph paragraph = builder.build()
121+
..layout(const ParagraphConstraints(width: 1000));
122+
// The height should be much smaller than fontSize * 10.
123+
expect(paragraph.height, lessThan(2 * fontSize));
124+
},
125+
skip: isHtml, // The HTML renderer does not support struts.
126+
);
84127
}

0 commit comments

Comments
 (0)