diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 2eb1d563179..be0cfa472bd 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -5ccee95373348854bc4877cfe240024a36847d2d +31b289f277c653ea91e20d75309958ac0dd0b865 diff --git a/packages/flutter/lib/src/painting/text_style.dart b/packages/flutter/lib/src/painting/text_style.dart index 1ea4d523bc0..0a928fda787 100644 --- a/packages/flutter/lib/src/painting/text_style.dart +++ b/packages/flutter/lib/src/painting/text_style.dart @@ -317,6 +317,7 @@ class TextStyle extends Diagnosticable { this.decoration, this.decorationColor, this.decorationStyle, + this.decorationThickness, this.debugLabel, String fontFamily, List fontFamilyFallback, @@ -482,6 +483,8 @@ class TextStyle extends Diagnosticable { final Paint background; /// The decorations to paint near the text (e.g., an underline). + /// + /// Multiple decorations can be applied using [TextDecoration.combine]. final TextDecoration decoration; /// The color in which to paint the text decorations. @@ -490,6 +493,51 @@ class TextStyle extends Diagnosticable { /// The style in which to paint the text decorations (e.g., dashed). final TextDecorationStyle decorationStyle; + /// The thickness of the decoration stroke as a muliplier of the thickness + /// defined by the font. + /// + /// The font provides a base stroke width for [decoration]s which scales off + /// of the [fontSize]. This property may be used to achieve a thinner or + /// thicker decoration stroke, without changing the [fontSize]. For example, + /// a [decorationThickness] of 2.0 will draw a decoration twice as thick as + /// the font defined decoration thickness. + /// + /// {@tool sample} + /// To achieve a bolded strike-through, we can apply a thicker stroke for the + /// decoration. + /// + /// ```dart + /// Text( + /// 'This has a very BOLD strike through!', + /// style: TextStyle( + /// decoration: TextDecoration.lineThrough, + /// decorationThickness: 2.85, + /// ), + /// ) + /// ``` + /// {@end-tool} + /// + /// {@tool sample} + /// We can apply a very thin and subtle wavy underline (perhaps, when words + /// are misspelled) by using a [decorationThickness] < 1.0. + /// + /// ```dart + /// Text( + /// 'oopsIforgottousespaces!', + /// style: TextStyle( + /// decoration: TextDecoration.underline, + /// decorationStyle: TextDecorationStyle.wavy, + /// decorationColor: Colors.red, + /// decorationThickness: 0.5, + /// ), + /// ) + /// ``` + /// {@end-tool} + /// + /// The default [decorationThickness] is 1.0, which will use the font's base + /// stroke thickness/width. + final double decorationThickness; + /// A human-readable description of this text style. /// /// This property is maintained only in debug builds. @@ -541,6 +589,7 @@ class TextStyle extends Diagnosticable { TextDecoration decoration, Color decorationColor, TextDecorationStyle decorationStyle, + double decorationThickness, String debugLabel, }) { assert(color == null || foreground == null, _kColorForegroundWarning); @@ -571,6 +620,7 @@ class TextStyle extends Diagnosticable { decoration: decoration ?? this.decoration, decorationColor: decorationColor ?? this.decorationColor, decorationStyle: decorationStyle ?? this.decorationStyle, + decorationThickness: decorationThickness ?? this.decorationThickness, debugLabel: newDebugLabel, ); } @@ -610,6 +660,8 @@ class TextStyle extends Diagnosticable { TextDecoration decoration, Color decorationColor, TextDecorationStyle decorationStyle, + double decorationThicknessFactor = 1.0, + double decorationThicknessDelta = 0.0, String fontFamily, List fontFamilyFallback, double fontSizeFactor = 1.0, @@ -636,6 +688,9 @@ class TextStyle extends Diagnosticable { assert(heightFactor != null); assert(heightDelta != null); assert(heightFactor != null || (heightFactor == 1.0 && heightDelta == 0.0)); + assert(decorationThicknessFactor != null); + assert(decorationThicknessDelta != null); + assert(decorationThickness != null || (decorationThicknessFactor == 1.0 && decorationThicknessDelta == 0.0)); String modifiedDebugLabel; assert(() { @@ -664,6 +719,7 @@ class TextStyle extends Diagnosticable { decoration: decoration ?? this.decoration, decorationColor: decorationColor ?? this.decorationColor, decorationStyle: decorationStyle ?? this.decorationStyle, + decorationThickness: decorationThickness == null ? null : decorationThickness * decorationThicknessFactor + decorationThicknessDelta, debugLabel: modifiedDebugLabel, ); } @@ -721,6 +777,7 @@ class TextStyle extends Diagnosticable { decoration: other.decoration, decorationColor: other.decorationColor, decorationStyle: other.decorationStyle, + decorationThickness: other.decorationThickness, debugLabel: mergedDebugLabel, ); } @@ -772,6 +829,7 @@ class TextStyle extends Diagnosticable { shadows: t < 0.5 ? null : b.shadows, decorationColor: Color.lerp(null, b.decorationColor, t), decorationStyle: t < 0.5 ? null : b.decorationStyle, + decorationThickness: t < 0.5 ? null : b.decorationThickness, debugLabel: lerpDebugLabel, ); } @@ -797,6 +855,7 @@ class TextStyle extends Diagnosticable { decoration: t < 0.5 ? a.decoration : null, decorationColor: Color.lerp(a.decorationColor, null, t), decorationStyle: t < 0.5 ? a.decorationStyle : null, + decorationThickness: t < 0.5 ? a.decorationThickness : null, debugLabel: lerpDebugLabel, ); } @@ -829,6 +888,7 @@ class TextStyle extends Diagnosticable { decoration: t < 0.5 ? a.decoration : b.decoration, decorationColor: Color.lerp(a.decorationColor, b.decorationColor, t), decorationStyle: t < 0.5 ? a.decorationStyle : b.decorationStyle, + decorationThickness: ui.lerpDouble(a.decorationThickness ?? b.decorationThickness, b.decorationThickness ?? a.decorationThickness, t), debugLabel: lerpDebugLabel, ); } @@ -840,6 +900,7 @@ class TextStyle extends Diagnosticable { decoration: decoration, decorationColor: decorationColor, decorationStyle: decorationStyle, + decorationThickness: decorationThickness, fontWeight: fontWeight, fontStyle: fontStyle, textBaseline: textBaseline, @@ -937,7 +998,8 @@ class TextStyle extends Diagnosticable { backgroundColor != other.backgroundColor || decoration != other.decoration || decorationColor != other.decorationColor || - decorationStyle != other.decorationStyle) + decorationStyle != other.decorationStyle || + decorationThickness != other.decorationThickness) return RenderComparison.paint; return RenderComparison.identical; } @@ -966,6 +1028,7 @@ class TextStyle extends Diagnosticable { decoration == typedOther.decoration && decorationColor == typedOther.decorationColor && decorationStyle == typedOther.decorationStyle && + decorationThickness == typedOther.decorationThickness && listEquals(shadows, typedOther.shadows) && listEquals(fontFamilyFallback, typedOther.fontFamilyFallback); } @@ -1031,7 +1094,7 @@ class TextStyle extends Diagnosticable { styles.add(DiagnosticsProperty('${prefix}locale', locale, defaultValue: null)); styles.add(DiagnosticsProperty('${prefix}foreground', foreground, defaultValue: null)); styles.add(DiagnosticsProperty('${prefix}background', background, defaultValue: null)); - if (decoration != null || decorationColor != null || decorationStyle != null) { + if (decoration != null || decorationColor != null || decorationStyle != null || decorationThickness != null) { final List decorationDescription = []; if (decorationStyle != null) decorationDescription.add(describeEnum(decorationStyle)); @@ -1051,6 +1114,7 @@ class TextStyle extends Diagnosticable { decorationDescription.add('$decoration'); assert(decorationDescription.isNotEmpty); styles.add(MessageProperty('${prefix}decoration', decorationDescription.join(' '))); + styles.add(DoubleProperty('${prefix}decorationThickness', decorationThickness, unit: 'x', defaultValue: null)); } final bool styleSpecified = styles.any((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info)); diff --git a/packages/flutter/test/material/theme_test.dart b/packages/flutter/test/material/theme_test.dart index 0c7996f40e6..54fe5ca9b1f 100644 --- a/packages/flutter/test/material/theme_test.dart +++ b/packages/flutter/test/material/theme_test.dart @@ -678,6 +678,8 @@ class _TextStyleProxy implements TextStyle { @override TextDecorationStyle get decorationStyle => _delegate.decorationStyle; @override + double get decorationThickness => _delegate.decorationThickness; + @override String get fontFamily => _delegate.fontFamily; @override List get fontFamilyFallback => _delegate.fontFamilyFallback; @@ -721,7 +723,26 @@ class _TextStyleProxy implements TextStyle { } @override - TextStyle apply({ Color color, Color backgroundColor, TextDecoration decoration, Color decorationColor, TextDecorationStyle decorationStyle, String fontFamily, List fontFamilyFallback, double fontSizeFactor = 1.0, double fontSizeDelta = 0.0, int fontWeightDelta = 0, double letterSpacingFactor = 1.0, double letterSpacingDelta = 0.0, double wordSpacingFactor = 1.0, double wordSpacingDelta = 0.0, double heightFactor = 1.0, double heightDelta = 0.0 }) { + TextStyle apply({ + Color color, + Color backgroundColor, + TextDecoration decoration, + Color decorationColor, + TextDecorationStyle decorationStyle, + double decorationThicknessFactor = 1.0, + double decorationThicknessDelta = 0.0, + String fontFamily, + List fontFamilyFallback, + double fontSizeFactor = 1.0, + double fontSizeDelta = 0.0, + int fontWeightDelta = 0, + double letterSpacingFactor = 1.0, + double letterSpacingDelta = 0.0, + double wordSpacingFactor = 1.0, + double wordSpacingDelta = 0.0, + double heightFactor = 1.0, + double heightDelta = 0.0 + }) { throw UnimplementedError(); } @@ -731,7 +752,29 @@ class _TextStyleProxy implements TextStyle { } @override - TextStyle copyWith({ bool inherit, Color color, Color backgroundColor, String fontFamily, List fontFamilyFallback, double fontSize, FontWeight fontWeight, FontStyle fontStyle, double letterSpacing, double wordSpacing, TextBaseline textBaseline, double height, Locale locale, ui.Paint foreground, ui.Paint background, List shadows, TextDecoration decoration, Color decorationColor, TextDecorationStyle decorationStyle, String debugLabel }) { + TextStyle copyWith({ + bool inherit, + Color color, + Color backgroundColor, + String fontFamily, + List fontFamilyFallback, + double fontSize, + FontWeight fontWeight, + FontStyle fontStyle, + double letterSpacing, + double wordSpacing, + TextBaseline textBaseline, + double height, + Locale locale, + ui.Paint foreground, + ui.Paint background, + List shadows, + TextDecoration decoration, + Color decorationColor, + TextDecorationStyle decorationStyle, + double decorationThickness, + String debugLabel + }) { throw UnimplementedError(); } @@ -741,7 +784,20 @@ class _TextStyleProxy implements TextStyle { } @override - ui.ParagraphStyle getParagraphStyle({ TextAlign textAlign, TextDirection textDirection, double textScaleFactor = 1.0, String ellipsis, int maxLines, Locale locale, String fontFamily, double fontSize, FontWeight fontWeight, FontStyle fontStyle, double height, StrutStyle strutStyle }) { + ui.ParagraphStyle getParagraphStyle({ + TextAlign textAlign, + TextDirection textDirection, + double textScaleFactor = 1.0, + String ellipsis, + int maxLines, + Locale locale, + String fontFamily, + double fontSize, + FontWeight fontWeight, + FontStyle fontStyle, + double height, + StrutStyle strutStyle + }) { throw UnimplementedError(); } diff --git a/packages/flutter/test/painting/text_style_test.dart b/packages/flutter/test/painting/text_style_test.dart index db35550606b..51b3f7cd855 100644 --- a/packages/flutter/test/painting/text_style_test.dart +++ b/packages/flutter/test/painting/text_style_test.dart @@ -169,10 +169,10 @@ void main() { final ui.TextStyle ts5 = s5.getTextStyle(); expect(ts5, equals(ui.TextStyle(fontWeight: FontWeight.w700, fontSize: 12.0, height: 123.0))); - expect(ts5.toString(), 'TextStyle(color: unspecified, decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, fontWeight: FontWeight.w700, fontStyle: unspecified, textBaseline: unspecified, fontFamily: unspecified, fontFamilyFallback: unspecified, fontSize: 12.0, letterSpacing: unspecified, wordSpacing: unspecified, height: 123.0x, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified)'); + expect(ts5.toString(), 'TextStyle(color: unspecified, decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, decorationThickness: unspecified, fontWeight: FontWeight.w700, fontStyle: unspecified, textBaseline: unspecified, fontFamily: unspecified, fontFamilyFallback: unspecified, fontSize: 12.0, letterSpacing: unspecified, wordSpacing: unspecified, height: 123.0x, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified)'); final ui.TextStyle ts2 = s2.getTextStyle(); expect(ts2, equals(ui.TextStyle(color: const Color(0xFF00FF00), fontWeight: FontWeight.w800, fontSize: 10.0, height: 100.0))); - expect(ts2.toString(), 'TextStyle(color: Color(0xff00ff00), decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, fontWeight: FontWeight.w800, fontStyle: unspecified, textBaseline: unspecified, fontFamily: unspecified, fontFamilyFallback: unspecified, fontSize: 10.0, letterSpacing: unspecified, wordSpacing: unspecified, height: 100.0x, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified)'); + expect(ts2.toString(), 'TextStyle(color: Color(0xff00ff00), decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, decorationThickness: unspecified, fontWeight: FontWeight.w800, fontStyle: unspecified, textBaseline: unspecified, fontFamily: unspecified, fontFamilyFallback: unspecified, fontSize: 10.0, letterSpacing: unspecified, wordSpacing: unspecified, height: 100.0x, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified)'); final ui.ParagraphStyle ps2 = s2.getParagraphStyle(textAlign: TextAlign.center); expect(ps2, equals(ui.ParagraphStyle(textAlign: TextAlign.center, fontWeight: FontWeight.w800, fontSize: 10.0, height: 100.0))); @@ -196,11 +196,11 @@ void main() { test('TextStyle using package font', () { const TextStyle s6 = TextStyle(fontFamily: 'test'); expect(s6.fontFamily, 'test'); - expect(s6.getTextStyle().toString(), 'TextStyle(color: unspecified, decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, fontWeight: unspecified, fontStyle: unspecified, textBaseline: unspecified, fontFamily: test, fontFamilyFallback: unspecified, fontSize: unspecified, letterSpacing: unspecified, wordSpacing: unspecified, height: unspecified, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified)'); + expect(s6.getTextStyle().toString(), 'TextStyle(color: unspecified, decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, decorationThickness: unspecified, fontWeight: unspecified, fontStyle: unspecified, textBaseline: unspecified, fontFamily: test, fontFamilyFallback: unspecified, fontSize: unspecified, letterSpacing: unspecified, wordSpacing: unspecified, height: unspecified, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified)'); const TextStyle s7 = TextStyle(fontFamily: 'test', package: 'p'); expect(s7.fontFamily, 'packages/p/test'); - expect(s7.getTextStyle().toString(), 'TextStyle(color: unspecified, decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, fontWeight: unspecified, fontStyle: unspecified, textBaseline: unspecified, fontFamily: packages/p/test, fontFamilyFallback: unspecified, fontSize: unspecified, letterSpacing: unspecified, wordSpacing: unspecified, height: unspecified, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified)'); + expect(s7.getTextStyle().toString(), 'TextStyle(color: unspecified, decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, decorationThickness: unspecified, fontWeight: unspecified, fontStyle: unspecified, textBaseline: unspecified, fontFamily: packages/p/test, fontFamilyFallback: unspecified, fontSize: unspecified, letterSpacing: unspecified, wordSpacing: unspecified, height: unspecified, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified)'); const TextStyle s8 = TextStyle(fontFamilyFallback: ['test', 'test2'], package: 'p'); expect(s8.fontFamilyFallback[0], 'packages/p/test'); @@ -236,7 +236,7 @@ void main() { expect(s4.fontFamilyFallback.isEmpty, true); final ui.TextStyle uis1 = s2.getTextStyle(); - expect(uis1.toString(), 'TextStyle(color: unspecified, decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, fontWeight: unspecified, fontStyle: unspecified, textBaseline: unspecified, fontFamily: foo, fontFamilyFallback: [Roboto, test], fontSize: unspecified, letterSpacing: unspecified, wordSpacing: unspecified, height: unspecified, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified)'); + expect(uis1.toString(), 'TextStyle(color: unspecified, decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, decorationThickness: unspecified, fontWeight: unspecified, fontStyle: unspecified, textBaseline: unspecified, fontFamily: foo, fontFamilyFallback: [Roboto, test], fontSize: unspecified, letterSpacing: unspecified, wordSpacing: unspecified, height: unspecified, locale: unspecified, background: unspecified, foreground: unspecified, shadows: unspecified)'); }); test('TextStyle.debugLabel', () { diff --git a/packages/flutter/test/widgets/text_golden_test.dart b/packages/flutter/test/widgets/text_golden_test.dart index 9f787952d15..5781c65aa1c 100644 --- a/packages/flutter/test/widgets/text_golden_test.dart +++ b/packages/flutter/test/widgets/text_golden_test.dart @@ -443,4 +443,81 @@ void main() { ); }, skip: true); // Should only be on linux (skip: !Platform.isLinux). // Disabled for now until font inconsistency is resolved. + + testWidgets('Decoration thickness', (WidgetTester tester) async { + final TextDecoration allDecorations = TextDecoration.combine( + [ + TextDecoration.underline, + TextDecoration.overline, + TextDecoration.lineThrough, + ] + ); + + await tester.pumpWidget( + Center( + child: RepaintBoundary( + child: Container( + width: 300.0, + height: 100.0, + decoration: const BoxDecoration( + color: Color(0xff00ff00), + ), + child: Text( + 'Hello, wor!\nabcd.', + style: TextStyle( + fontSize: 25, + decoration: allDecorations, + decorationColor: Colors.blue, + decorationStyle: TextDecorationStyle.dashed, + ), + textDirection: TextDirection.rtl, + ), + ), + ), + ), + ); + await expectLater( + find.byType(Container), + matchesGoldenFile('text_golden.Decoration.1.0.png'), + ); + }, skip: !Platform.isLinux); // Coretext uses different thicknesses for decoration + + testWidgets('Decoration thickness', (WidgetTester tester) async { + final TextDecoration allDecorations = TextDecoration.combine( + [ + TextDecoration.underline, + TextDecoration.overline, + TextDecoration.lineThrough, + ] + ); + + await tester.pumpWidget( + Center( + child: RepaintBoundary( + child: Container( + width: 300.0, + height: 100.0, + decoration: const BoxDecoration( + color: Color(0xff00ff00), + ), + child: Text( + 'Hello, wor!\nabcd.', + style: TextStyle( + fontSize: 25, + decoration: allDecorations, + decorationColor: Colors.blue, + decorationStyle: TextDecorationStyle.wavy, + decorationThickness: 4, + ), + textDirection: TextDirection.rtl, + ), + ), + ), + ), + ); + await expectLater( + find.byType(Container), + matchesGoldenFile('text_golden.DecorationThickness.1.0.png'), + ); + }, skip: !Platform.isLinux); // Coretext uses different thicknesses for decoration }