From ac95d4bf7122a29697e4a95512c0cdc5341b60f2 Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Wed, 10 Jan 2024 17:31:32 -0800 Subject: [PATCH 1/4] fix ParagraphStyle for CanvasKit and HTML --- lib/web_ui/lib/src/engine/canvaskit/text.dart | 250 +++++++++++++----- lib/web_ui/lib/src/engine/text/paragraph.dart | 26 +- lib/web_ui/test/ui/paragraph_style_test.dart | 162 ++++++++++++ .../test/ui/text_style_test_env_test.dart | 39 +++ 4 files changed, 395 insertions(+), 82 deletions(-) create mode 100644 lib/web_ui/test/ui/paragraph_style_test.dart create mode 100644 lib/web_ui/test/ui/text_style_test_env_test.dart diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index 25c6193869c67..7740af376728b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -12,7 +12,7 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web; final bool _ckRequiresClientICU = canvasKit.ParagraphBuilder.RequiresClientICU(); final List _testFonts = ['FlutterTest', 'Ahem']; -String? _effectiveFontFamily(String? fontFamily) { +String? _computeEffectiveFontFamily(String? fontFamily) { return ui_web.debugEmulateFlutterTesterEnvironment && !_testFonts.contains(fontFamily) ? _testFonts.first : fontFamily; @@ -38,7 +38,7 @@ class CkParagraphStyle implements ui.ParagraphStyle { textAlign, textDirection, maxLines, - _effectiveFontFamily(fontFamily), + _computeEffectiveFontFamily(fontFamily), fontSize, height, textHeightBehavior, @@ -49,20 +49,36 @@ class CkParagraphStyle implements ui.ParagraphStyle { locale, applyRoundingHack, ), - _fontFamily = _effectiveFontFamily(fontFamily), + _textAlign = textAlign, + _textDirection = textDirection, + _fontWeight = fontWeight, + _fontStyle = fontStyle, + _maxLines = maxLines, + _originalFontFamily = fontFamily, + _effectiveFontFamily = _computeEffectiveFontFamily(fontFamily), _fontSize = fontSize, _height = height, - _leadingDistribution = textHeightBehavior?.leadingDistribution, - _fontWeight = fontWeight, - _fontStyle = fontStyle; + _textHeightBehavior = textHeightBehavior, + _strutStyle = strutStyle, + _ellipsis = ellipsis, + _locale = locale; + final SkParagraphStyle skParagraphStyle; - final String? _fontFamily; - final double? _fontSize; - final double? _height; + + final ui.TextAlign? _textAlign; + final ui.TextDirection? _textDirection; final ui.FontWeight? _fontWeight; final ui.FontStyle? _fontStyle; - final ui.TextLeadingDistribution? _leadingDistribution; + final int? _maxLines; + final String? _originalFontFamily; + final String? _effectiveFontFamily; + final double? _fontSize; + final double? _height; + final ui.TextHeightBehavior? _textHeightBehavior; + final ui.StrutStyle? _strutStyle; + final String? _ellipsis; + final ui.Locale? _locale; static SkTextStyleProperties toSkTextStyleProperties( String? fontFamily, @@ -190,15 +206,101 @@ class CkParagraphStyle implements ui.ParagraphStyle { } CkTextStyle getTextStyle() { - return CkTextStyle( - fontFamily: _fontFamily, + return CkTextStyle._( + originalFontFamily: _originalFontFamily, + effectiveFontFamily: _effectiveFontFamily, fontSize: _fontSize, height: _height, - leadingDistribution: _leadingDistribution, + leadingDistribution: _textHeightBehavior?.leadingDistribution, fontWeight: _fontWeight, fontStyle: _fontStyle, + + // Use defaults for everything else. + color: null, + decoration: null, + decorationColor: null, + decorationStyle: null, + decorationThickness: null, + textBaseline: null, + fontFamilyFallback: null, + letterSpacing: null, + wordSpacing: null, + locale: null, + background: null, + foreground: null, + shadows: null, + fontFeatures: null, + fontVariations: null, ); } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is CkParagraphStyle && + other._textAlign == _textAlign && + other._textDirection == _textDirection && + other._fontWeight == _fontWeight && + other._fontStyle == _fontStyle && + other._maxLines == _maxLines && + other._originalFontFamily == _originalFontFamily && + // effectiveFontFamily is not compared as it's a computed value. + other._fontSize == _fontSize && + other._height == _height && + other._textHeightBehavior == _textHeightBehavior && + other._strutStyle == _strutStyle && + other._ellipsis == _ellipsis && + other._locale == _locale; + } + + @override + int get hashCode { + return Object.hash( + _textAlign, + _textDirection, + _fontWeight, + _fontStyle, + _maxLines, + _originalFontFamily, + // effectiveFontFamily is not included as it's a computed value. + _fontSize, + _height, + _textHeightBehavior, + _strutStyle, + _ellipsis, + _locale, + ); + } + + @override + String toString() { + String result = super.toString(); + assert(() { + final double? fontSize = _fontSize; + final double? height = _height; + result = 'ParagraphStyle(' + 'textAlign: ${_textAlign ?? "unspecified"}, ' + 'textDirection: ${_textDirection ?? "unspecified"}, ' + 'fontWeight: ${_fontWeight ?? "unspecified"}, ' + 'fontStyle: ${_fontStyle ?? "unspecified"}, ' + 'maxLines: ${_maxLines ?? "unspecified"}, ' + 'textHeightBehavior: ${_textHeightBehavior ?? "unspecified"}, ' + 'fontFamily: ${_originalFontFamily ?? "unspecified"}, ' + 'fontSize: ${fontSize != null ? fontSize.toStringAsFixed(1) : "unspecified"}, ' + 'height: ${height != null ? "${height.toStringAsFixed(1)}x" : "unspecified"}, ' + 'strutStyle: ${_strutStyle ?? "unspecified"}, ' + 'ellipsis: ${_ellipsis != null ? '"$_ellipsis"' : "unspecified"}, ' + 'locale: ${_locale ?? "unspecified"}' + ')'; + return true; + }()); + return result; + } } @immutable @@ -232,53 +334,55 @@ class CkTextStyle implements ui.TextStyle { 'The color argument is just a shorthand for "foreground: Paint()..color = color".', ); return CkTextStyle._( - color, - decoration, - decorationColor, - decorationStyle, - decorationThickness, - fontWeight, - fontStyle, - textBaseline, - _effectiveFontFamily(fontFamily), - ui_web.debugEmulateFlutterTesterEnvironment ? null : fontFamilyFallback, - fontSize, - letterSpacing, - wordSpacing, - height, - leadingDistribution, - locale, - background, - foreground, - shadows, - fontFeatures, - fontVariations, + color: color, + decoration: decoration, + decorationColor: decorationColor, + decorationStyle: decorationStyle, + decorationThickness: decorationThickness, + fontWeight: fontWeight, + fontStyle: fontStyle, + textBaseline: textBaseline, + originalFontFamily: fontFamily, + effectiveFontFamily: _computeEffectiveFontFamily(fontFamily), + fontFamilyFallback: ui_web.debugEmulateFlutterTesterEnvironment ? null : fontFamilyFallback, + fontSize: fontSize, + letterSpacing: letterSpacing, + wordSpacing: wordSpacing, + height: height, + leadingDistribution: leadingDistribution, + locale: locale, + background: background, + foreground: foreground, + shadows: shadows, + fontFeatures: fontFeatures, + fontVariations: fontVariations, ); } - CkTextStyle._( - this.color, - this.decoration, - this.decorationColor, - this.decorationStyle, - this.decorationThickness, - this.fontWeight, - this.fontStyle, - this.textBaseline, - this.fontFamily, - this.fontFamilyFallback, - this.fontSize, - this.letterSpacing, - this.wordSpacing, - this.height, - this.leadingDistribution, - this.locale, - this.background, - this.foreground, - this.shadows, - this.fontFeatures, - this.fontVariations, - ); + CkTextStyle._({ + required this.color, + required this.decoration, + required this.decorationColor, + required this.decorationStyle, + required this.decorationThickness, + required this.fontWeight, + required this.fontStyle, + required this.textBaseline, + required this.originalFontFamily, + required this.effectiveFontFamily, + required this.fontFamilyFallback, + required this.fontSize, + required this.letterSpacing, + required this.wordSpacing, + required this.height, + required this.leadingDistribution, + required this.locale, + required this.background, + required this.foreground, + required this.shadows, + required this.fontFeatures, + required this.fontVariations, + }); final ui.Color? color; final ui.TextDecoration? decoration; @@ -288,7 +392,8 @@ class CkTextStyle implements ui.TextStyle { final ui.FontWeight? fontWeight; final ui.FontStyle? fontStyle; final ui.TextBaseline? textBaseline; - final String? fontFamily; + final String? originalFontFamily; + final String? effectiveFontFamily; final List? fontFamilyFallback; final double? fontSize; final double? letterSpacing; @@ -307,7 +412,7 @@ class CkTextStyle implements ui.TextStyle { /// The values in this text style are used unless [other] specifically /// overrides it. CkTextStyle mergeWith(CkTextStyle other) { - return CkTextStyle( + return CkTextStyle._( color: other.color ?? color, decoration: other.decoration ?? decoration, decorationColor: other.decorationColor ?? decorationColor, @@ -316,7 +421,8 @@ class CkTextStyle implements ui.TextStyle { fontWeight: other.fontWeight ?? fontWeight, fontStyle: other.fontStyle ?? fontStyle, textBaseline: other.textBaseline ?? textBaseline, - fontFamily: other.fontFamily ?? fontFamily, + originalFontFamily: other.originalFontFamily ?? originalFontFamily, + effectiveFontFamily: other.effectiveFontFamily ?? effectiveFontFamily, fontFamilyFallback: other.fontFamilyFallback ?? fontFamilyFallback, fontSize: other.fontSize ?? fontSize, letterSpacing: other.letterSpacing ?? letterSpacing, @@ -334,7 +440,7 @@ class CkTextStyle implements ui.TextStyle { /// Lazy-initialized list of font families sent to Skia. late final List effectiveFontFamilies = - _getEffectiveFontFamilies(fontFamily, fontFamilyFallback); + _getEffectiveFontFamilies(effectiveFontFamily, fontFamilyFallback); /// Lazy-initialized Skia style used to pass the style to Skia. /// @@ -491,7 +597,7 @@ class CkTextStyle implements ui.TextStyle { && other.fontStyle == fontStyle && other.textBaseline == textBaseline && other.leadingDistribution == leadingDistribution - && other.fontFamily == fontFamily + && other.originalFontFamily == originalFontFamily && other.fontSize == fontSize && other.letterSpacing == letterSpacing && other.wordSpacing == wordSpacing @@ -521,7 +627,7 @@ class CkTextStyle implements ui.TextStyle { fontStyle, textBaseline, leadingDistribution, - fontFamily, + originalFontFamily, fontFamilyFallback == null ? null : Object.hashAll(fontFamilyFallback), fontSize, letterSpacing, @@ -543,7 +649,7 @@ class CkTextStyle implements ui.TextStyle { @override String toString() { final List? fontFamilyFallback = this.fontFamilyFallback; - final String? fontFamily = this.fontFamily; + final String? originalFontFamily = this.originalFontFamily; return 'TextStyle(' 'color: ${color ?? "unspecified"}, ' 'decoration: ${decoration ?? "unspecified"}, ' @@ -553,7 +659,7 @@ class CkTextStyle implements ui.TextStyle { 'fontWeight: ${fontWeight ?? "unspecified"}, ' 'fontStyle: ${fontStyle ?? "unspecified"}, ' 'textBaseline: ${textBaseline ?? "unspecified"}, ' - 'fontFamily: ${fontFamily != null && fontFamily.isNotEmpty ? fontFamily : "unspecified"}, ' + 'fontFamily: ${originalFontFamily != null && originalFontFamily.isNotEmpty ? originalFontFamily : "unspecified"}, ' 'fontFamilyFallback: ${fontFamilyFallback != null && fontFamilyFallback.isNotEmpty ? fontFamilyFallback : "unspecified"}, ' 'fontSize: ${fontSize ?? "unspecified"}, ' 'letterSpacing: ${letterSpacing != null ? "${letterSpacing}x" : "unspecified"}, ' @@ -582,7 +688,7 @@ class CkStrutStyle implements ui.StrutStyle { ui.FontWeight? fontWeight, ui.FontStyle? fontStyle, bool? forceStrutHeight, - }) : _fontFamily = _effectiveFontFamily(fontFamily), + }) : _fontFamily = _computeEffectiveFontFamily(fontFamily), _fontFamilyFallback = ui_web.debugEmulateFlutterTesterEnvironment ? null : fontFamilyFallback, _fontSize = fontSize, _height = height, @@ -806,9 +912,11 @@ class CkParagraph implements ui.Paragraph { _boxesForPlaceholders = skRectsToTextBoxes(paragraph.getRectsForPlaceholders()); } catch (e) { - printWarning('CanvasKit threw an exception while laying ' - 'out the paragraph. The font was "${_paragraphStyle._fontFamily}". ' - 'Exception:\n$e'); + printWarning( + 'CanvasKit threw an exception while laying ' + 'out the paragraph. The font was "${_paragraphStyle._originalFontFamily}". ' + 'Exception:\n$e', + ); rethrow; } } @@ -993,8 +1101,8 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { void addText(String text) { final List fontFamilies = []; final CkTextStyle style = _peekStyle(); - if (style.fontFamily != null) { - fontFamilies.add(style.fontFamily!); + if (style.effectiveFontFamily != null) { + fontFamilies.add(style.effectiveFontFamily!); } if (style.fontFamilyFallback != null) { fontFamilies.addAll(style.fontFamilyFallback!); diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index 27ea726c9cc1e..be4b305df539c 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -501,6 +501,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle { other.fontSize == fontSize && other.height == height && other._textHeightBehavior == _textHeightBehavior && + other._strutStyle == _strutStyle && other.ellipsis == ellipsis && other.locale == locale; } @@ -508,17 +509,19 @@ class EngineParagraphStyle implements ui.ParagraphStyle { @override int get hashCode { return Object.hash( - textAlign, - textDirection, - fontWeight, - fontStyle, - maxLines, - fontFamily, - fontSize, - height, - _textHeightBehavior, - ellipsis, - locale); + textAlign, + textDirection, + fontWeight, + fontStyle, + maxLines, + fontFamily, + fontSize, + height, + _textHeightBehavior, + _strutStyle, + ellipsis, + locale, + ); } @override @@ -537,6 +540,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle { 'fontFamily: ${fontFamily ?? "unspecified"}, ' 'fontSize: ${fontSize != null ? fontSize.toStringAsFixed(1) : "unspecified"}, ' 'height: ${height != null ? "${height.toStringAsFixed(1)}x" : "unspecified"}, ' + 'strutStyle: ${_strutStyle ?? "unspecified"}, ' 'ellipsis: ${ellipsis != null ? '"$ellipsis"' : "unspecified"}, ' 'locale: ${locale ?? "unspecified"}' ')'; diff --git a/lib/web_ui/test/ui/paragraph_style_test.dart b/lib/web_ui/test/ui/paragraph_style_test.dart new file mode 100644 index 0000000000000..26e573eefd707 --- /dev/null +++ b/lib/web_ui/test/ui/paragraph_style_test.dart @@ -0,0 +1,162 @@ +// 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. + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/ui.dart' as ui; + +import '../common/test_initialization.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +Future testMain() async { + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); + + test('blanks are equal to each other', () { + final ui.ParagraphStyle a = ui.ParagraphStyle(); + final ui.ParagraphStyle b = ui.ParagraphStyle(); + expect(a, b); + expect(a.hashCode, b.hashCode); + }); + + test('each property individually equal', () { + for (final String property in _populatorsA.keys) { + final _ParagraphStylePropertyPopulator populator = _populatorsA[property]!; + + final _TestParagraphStyleBuilder aBuilder = _TestParagraphStyleBuilder(); + populator(aBuilder); + final ui.ParagraphStyle a = aBuilder.build(); + + final _TestParagraphStyleBuilder bBuilder = _TestParagraphStyleBuilder(); + populator(bBuilder); + final ui.ParagraphStyle b = bBuilder.build(); + + expect(reason: '$property property is equal', a, b); + expect(reason: '$property hashCode is equal', a.hashCode, b.hashCode); + } + }); + + test('each property individually not equal', () { + for (final String property in _populatorsA.keys) { + final _ParagraphStylePropertyPopulator populatorA = _populatorsA[property]!; + + final _TestParagraphStyleBuilder aBuilder = _TestParagraphStyleBuilder(); + populatorA(aBuilder); + final ui.ParagraphStyle a = aBuilder.build(); + + final _ParagraphStylePropertyPopulator populatorB = _populatorsB[property]!; + final _TestParagraphStyleBuilder bBuilder = _TestParagraphStyleBuilder(); + populatorB(bBuilder); + final ui.ParagraphStyle b = bBuilder.build(); + + expect(reason: '$property property is not equal', a, isNot(b)); + expect(reason: '$property hashCode is not equal', a.hashCode, isNot(b.hashCode)); + } + }); + + test('all properties altogether equal', () { + final _TestParagraphStyleBuilder aBuilder = _TestParagraphStyleBuilder(); + final _TestParagraphStyleBuilder bBuilder = _TestParagraphStyleBuilder(); + + for (final String property in _populatorsA.keys) { + final _ParagraphStylePropertyPopulator populator = _populatorsA[property]!; + populator(aBuilder); + populator(bBuilder); + } + + final ui.ParagraphStyle a = aBuilder.build(); + final ui.ParagraphStyle b = bBuilder.build(); + + expect(a, b); + expect(a.hashCode, b.hashCode); + }); + + test('all properties altogether not equal', () { + final _TestParagraphStyleBuilder aBuilder = _TestParagraphStyleBuilder(); + final _TestParagraphStyleBuilder bBuilder = _TestParagraphStyleBuilder(); + + for (final String property in _populatorsA.keys) { + final _ParagraphStylePropertyPopulator populatorA = _populatorsA[property]!; + populatorA(aBuilder); + + final _ParagraphStylePropertyPopulator populatorB = _populatorsB[property]!; + populatorB(bBuilder); + } + + final ui.ParagraphStyle a = aBuilder.build(); + final ui.ParagraphStyle b = bBuilder.build(); + + expect(a, isNot(b)); + expect(a.hashCode, isNot(b.hashCode)); + }); +} + +typedef _ParagraphStylePropertyPopulator = void Function(_TestParagraphStyleBuilder builder); + +final Map _populatorsA = { + 'textAlign': (_TestParagraphStyleBuilder builder) { builder.textAlign = ui.TextAlign.left; }, + 'textDirection': (_TestParagraphStyleBuilder builder) { builder.textDirection = ui.TextDirection.rtl; }, + 'fontWeight': (_TestParagraphStyleBuilder builder) { builder.fontWeight = ui.FontWeight.w400; }, + 'fontStyle': (_TestParagraphStyleBuilder builder) { builder.fontStyle = ui.FontStyle.normal; }, + 'maxLines': (_TestParagraphStyleBuilder builder) { builder.maxLines = 1; }, + 'fontFamily': (_TestParagraphStyleBuilder builder) { builder.fontFamily = 'Arial'; }, + 'fontSize': (_TestParagraphStyleBuilder builder) { builder.fontSize = 12; }, + 'height': (_TestParagraphStyleBuilder builder) { builder.height = 13; }, + 'textHeightBehavior': (_TestParagraphStyleBuilder builder) { builder.textHeightBehavior = const ui.TextHeightBehavior(); }, + 'strutStyle': (_TestParagraphStyleBuilder builder) { builder.strutStyle = ui.StrutStyle(fontFamily: 'Times New Roman'); }, + 'ellipsis': (_TestParagraphStyleBuilder builder) { builder.ellipsis = '...'; }, + 'locale': (_TestParagraphStyleBuilder builder) { builder.locale = const ui.Locale('en', 'US'); }, +}; + +final Map _populatorsB = { + 'textAlign': (_TestParagraphStyleBuilder builder) { builder.textAlign = ui.TextAlign.right; }, + 'textDirection': (_TestParagraphStyleBuilder builder) { builder.textDirection = ui.TextDirection.ltr; }, + 'fontWeight': (_TestParagraphStyleBuilder builder) { builder.fontWeight = ui.FontWeight.w600; }, + 'fontStyle': (_TestParagraphStyleBuilder builder) { builder.fontStyle = ui.FontStyle.italic; }, + 'maxLines': (_TestParagraphStyleBuilder builder) { builder.maxLines = 2; }, + 'fontFamily': (_TestParagraphStyleBuilder builder) { builder.fontFamily = 'Noto'; }, + 'fontSize': (_TestParagraphStyleBuilder builder) { builder.fontSize = 12.1; }, + 'height': (_TestParagraphStyleBuilder builder) { builder.height = 13.1; }, + 'textHeightBehavior': (_TestParagraphStyleBuilder builder) { builder.textHeightBehavior = const ui.TextHeightBehavior(applyHeightToFirstAscent: false); }, + 'strutStyle': (_TestParagraphStyleBuilder builder) { builder.strutStyle = ui.StrutStyle(fontFamily: 'sans-serif'); }, + 'ellipsis': (_TestParagraphStyleBuilder builder) { builder.ellipsis = '___'; }, + 'locale': (_TestParagraphStyleBuilder builder) { builder.locale = const ui.Locale('fr', 'CA'); }, +}; + +class _TestParagraphStyleBuilder { + ui.TextAlign? textAlign; + ui.TextDirection? textDirection; + ui.FontWeight? fontWeight; + ui.FontStyle? fontStyle; + int? maxLines; + String? fontFamily; + double? fontSize; + double? height; + ui.TextHeightBehavior? textHeightBehavior; + ui.StrutStyle? strutStyle; + String? ellipsis; + ui.Locale? locale; + + ui.ParagraphStyle build() { + return ui.ParagraphStyle( + textAlign: textAlign, + textDirection: textDirection, + fontWeight: fontWeight, + fontStyle: fontStyle, + maxLines: maxLines, + fontFamily: fontFamily, + fontSize: fontSize, + height: height, + textHeightBehavior: textHeightBehavior, + strutStyle: strutStyle, + ellipsis: ellipsis, + locale: locale, + ); + } +} diff --git a/lib/web_ui/test/ui/text_style_test_env_test.dart b/lib/web_ui/test/ui/text_style_test_env_test.dart new file mode 100644 index 0000000000000..b65946aed1dc8 --- /dev/null +++ b/lib/web_ui/test/ui/text_style_test_env_test.dart @@ -0,0 +1,39 @@ +// 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. + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/ui.dart' as ui; + +import '../common/test_initialization.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +Future testMain() async { + setUpUnitTests(setUpTestViewDimensions: false); + + // Previously the logic that set the effective font family would forget the + // original value and would print incorrect value in toString. + test('TextStyle remembers original fontFamily value', () { + final ui.TextStyle style1 = ui.TextStyle(); + expect(style1.toString(), contains('fontFamily: unspecified')); + + final ui.TextStyle style2 = ui.TextStyle( + fontFamily: 'Hello', + ); + expect(style2.toString(), contains('fontFamily: Hello')); + }); + + test('ParagraphStyle remembers original fontFamily value', () { + final ui.ParagraphStyle style1 = ui.ParagraphStyle(); + expect(style1.toString(), contains('fontFamily: unspecified')); + + final ui.ParagraphStyle style2 = ui.ParagraphStyle( + fontFamily: 'Hello', + ); + expect(style2.toString(), contains('fontFamily: Hello')); + }); +} From 96dc0dbaac4f2ad3886297c2523374b1d7d01976 Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Sun, 14 Jan 2024 14:38:39 -0800 Subject: [PATCH 2/4] fix StrutStyle and SwkasmParagraphStyle --- lib/web_ui/lib/src/engine/canvaskit/text.dart | 2 +- .../engine/skwasm/skwasm_impl/paragraph.dart | 178 +++++++++++++++++- lib/web_ui/lib/src/engine/text/paragraph.dart | 2 +- lib/web_ui/test/ui/strut_style_test.dart | 152 +++++++++++++++ lib/web_ui/test/ui/text_style_test.dart | 2 + 5 files changed, 328 insertions(+), 8 deletions(-) create mode 100644 lib/web_ui/test/ui/strut_style_test.dart diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index 7740af376728b..e4857f9d4bcc5 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -728,7 +728,7 @@ class CkStrutStyle implements ui.StrutStyle { @override int get hashCode => Object.hash( _fontFamily, - _fontFamilyFallback, + _fontFamilyFallback != null ? Object.hashAll(_fontFamilyFallback) : null, _fontSize, _height, _leading, diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index fe7f0f85cde17..d107a39d2d1ca 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -549,17 +549,17 @@ class SkwasmTextStyle implements ui.TextStyle { } } -class SkwasmStrutStyle extends SkwasmObjectWrapper implements ui.StrutStyle { +final class SkwasmStrutStyle extends SkwasmObjectWrapper implements ui.StrutStyle { factory SkwasmStrutStyle({ String? fontFamily, List? fontFamilyFallback, double? fontSize, double? height, - ui.TextLeadingDistribution? leadingDistribution, double? leading, ui.FontWeight? fontWeight, ui.FontStyle? fontStyle, bool? forceStrutHeight, + ui.TextLeadingDistribution? leadingDistribution, }) { final StrutStyleHandle handle = strutStyleCreate(); if (fontFamily != null || fontFamilyFallback != null) { @@ -595,13 +595,72 @@ class SkwasmStrutStyle extends SkwasmObjectWrapper implements ui. if (forceStrutHeight != null) { strutStyleSetForceStrutHeight(handle, forceStrutHeight); } - return SkwasmStrutStyle._(handle); + return SkwasmStrutStyle._( + handle, + fontFamily, + fontFamilyFallback, + fontSize, + height, + leadingDistribution, + leading, + fontWeight, + fontStyle, + forceStrutHeight, + ); } - SkwasmStrutStyle._(StrutStyleHandle handle) : super(handle, _registry); + SkwasmStrutStyle._( + StrutStyleHandle handle, + this._fontFamily, + this._fontFamilyFallback, + this._fontSize, + this._height, + this._leadingDistribution, + this._leading, + this._fontWeight, + this._fontStyle, + this._forceStrutHeight, + ) : super(handle, _registry); static final SkwasmFinalizationRegistry _registry = SkwasmFinalizationRegistry(strutStyleDispose); + + final String? _fontFamily; + final List? _fontFamilyFallback; + final double? _fontSize; + final double? _height; + final double? _leading; + final ui.FontWeight? _fontWeight; + final ui.FontStyle? _fontStyle; + final bool? _forceStrutHeight; + final ui.TextLeadingDistribution? _leadingDistribution; + + @override + bool operator ==(Object other) { + return other is SkwasmStrutStyle && + other._fontFamily == _fontFamily && + other._fontSize == _fontSize && + other._height == _height && + other._leading == _leading && + other._leadingDistribution == _leadingDistribution && + other._fontWeight == _fontWeight && + other._fontStyle == _fontStyle && + other._forceStrutHeight == _forceStrutHeight && + listEquals(other._fontFamilyFallback, _fontFamilyFallback); + } + + @override + int get hashCode => Object.hash( + _fontFamily, + _fontFamilyFallback != null ? Object.hashAll(_fontFamilyFallback) : null, + _fontSize, + _height, + _leading, + _leadingDistribution, + _fontWeight, + _fontStyle, + _forceStrutHeight, + ); } class SkwasmParagraphStyle extends SkwasmObjectWrapper implements ui.ParagraphStyle { @@ -678,13 +737,41 @@ class SkwasmParagraphStyle extends SkwasmObjectWrapper implem skStringFree(localeHandle); } paragraphStyleSetTextStyle(handle, textStyleHandle); - return SkwasmParagraphStyle._(handle, textStyle, fontFamily); + return SkwasmParagraphStyle._( + handle, + textStyle, + fontFamily, + textAlign, + textDirection, + fontWeight, + fontStyle, + maxLines, + fontFamily, + fontSize, + height, + textHeightBehavior, + strutStyle, + ellipsis, + locale, + ); } SkwasmParagraphStyle._( ParagraphStyleHandle handle, this.textStyle, - this.defaultFontFamily + this.defaultFontFamily, + this._textAlign, + this._textDirection, + this._fontWeight, + this._fontStyle, + this._maxLines, + this._fontFamily, + this._fontSize, + this._height, + this._textHeightBehavior, + this._strutStyle, + this._ellipsis, + this._locale, ) : super(handle, _registry); static final SkwasmFinalizationRegistry _registry = @@ -692,6 +779,85 @@ class SkwasmParagraphStyle extends SkwasmObjectWrapper implem final SkwasmNativeTextStyle textStyle; final String? defaultFontFamily; + + final ui.TextAlign? _textAlign; + final ui.TextDirection? _textDirection; + final ui.FontWeight? _fontWeight; + final ui.FontStyle? _fontStyle; + final int? _maxLines; + final String? _fontFamily; + final double? _fontSize; + final double? _height; + final ui.TextHeightBehavior? _textHeightBehavior; + final ui.StrutStyle? _strutStyle; + final String? _ellipsis; + final ui.Locale? _locale; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is SkwasmParagraphStyle && + other._textAlign == _textAlign && + other._textDirection == _textDirection && + other._fontWeight == _fontWeight && + other._fontStyle == _fontStyle && + other._maxLines == _maxLines && + other._fontFamily == _fontFamily && + other._fontSize == _fontSize && + other._height == _height && + other._textHeightBehavior == _textHeightBehavior && + other._strutStyle == _strutStyle && + other._ellipsis == _ellipsis && + other._locale == _locale; + } + + @override + int get hashCode { + return Object.hash( + _textAlign, + _textDirection, + _fontWeight, + _fontStyle, + _maxLines, + _fontFamily, + _fontSize, + _height, + _textHeightBehavior, + _strutStyle, + _ellipsis, + _locale, + ); + } + + @override + String toString() { + String result = super.toString(); + assert(() { + final double? fontSize = _fontSize; + final double? height = _height; + result = 'ParagraphStyle(' + 'textAlign: ${_textAlign ?? "unspecified"}, ' + 'textDirection: ${_textDirection ?? "unspecified"}, ' + 'fontWeight: ${_fontWeight ?? "unspecified"}, ' + 'fontStyle: ${_fontStyle ?? "unspecified"}, ' + 'maxLines: ${_maxLines ?? "unspecified"}, ' + 'textHeightBehavior: ${_textHeightBehavior ?? "unspecified"}, ' + 'fontFamily: ${_fontFamily ?? "unspecified"}, ' + 'fontSize: ${fontSize != null ? fontSize.toStringAsFixed(1) : "unspecified"}, ' + 'height: ${height != null ? "${height.toStringAsFixed(1)}x" : "unspecified"}, ' + 'strutStyle: ${_strutStyle ?? "unspecified"}, ' + 'ellipsis: ${_ellipsis != null ? '"$_ellipsis"' : "unspecified"}, ' + 'locale: ${_locale ?? "unspecified"}' + ')'; + return true; + }()); + return result; + } } class SkwasmParagraphBuilder extends SkwasmObjectWrapper implements ui.ParagraphBuilder { diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index be4b305df539c..6eff331c879cf 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -848,7 +848,7 @@ class EngineStrutStyle implements ui.StrutStyle { @override int get hashCode => Object.hash( _fontFamily, - _fontFamilyFallback, + _fontFamilyFallback != null ? Object.hashAll(_fontFamilyFallback) : null, _fontSize, _height, _leading, diff --git a/lib/web_ui/test/ui/strut_style_test.dart b/lib/web_ui/test/ui/strut_style_test.dart new file mode 100644 index 0000000000000..4d182e6f65ce3 --- /dev/null +++ b/lib/web_ui/test/ui/strut_style_test.dart @@ -0,0 +1,152 @@ +// 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. + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/ui.dart' as ui; + +import '../common/test_initialization.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +Future testMain() async { + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); + + test('blanks are equal to each other', () { + final ui.StrutStyle a = ui.StrutStyle(); + final ui.StrutStyle b = ui.StrutStyle(); + expect(a, b); + expect(a.hashCode, b.hashCode); + }); + + test('each property individually equal', () { + for (final String property in _populatorsA.keys) { + final _StrutStylePropertyPopulator populator = _populatorsA[property]!; + + final _TestStrutStyleBuilder aBuilder = _TestStrutStyleBuilder(); + populator(aBuilder); + final ui.StrutStyle a = aBuilder.build(); + + final _TestStrutStyleBuilder bBuilder = _TestStrutStyleBuilder(); + populator(bBuilder); + final ui.StrutStyle b = bBuilder.build(); + + expect(reason: '$property property is equal', a, b); + expect(reason: '$property hashCode is equal', a.hashCode, b.hashCode); + } + }); + + test('each property individually not equal', () { + for (final String property in _populatorsA.keys) { + final _StrutStylePropertyPopulator populatorA = _populatorsA[property]!; + + final _TestStrutStyleBuilder aBuilder = _TestStrutStyleBuilder(); + populatorA(aBuilder); + final ui.StrutStyle a = aBuilder.build(); + + final _StrutStylePropertyPopulator populatorB = _populatorsB[property]!; + final _TestStrutStyleBuilder bBuilder = _TestStrutStyleBuilder(); + populatorB(bBuilder); + final ui.StrutStyle b = bBuilder.build(); + + expect(reason: '$property property is not equal', a, isNot(b)); + expect(reason: '$property hashCode is not equal', a.hashCode, isNot(b.hashCode)); + } + }); + + test('all properties altogether equal', () { + final _TestStrutStyleBuilder aBuilder = _TestStrutStyleBuilder(); + final _TestStrutStyleBuilder bBuilder = _TestStrutStyleBuilder(); + + for (final String property in _populatorsA.keys) { + final _StrutStylePropertyPopulator populator = _populatorsA[property]!; + populator(aBuilder); + populator(bBuilder); + } + + final ui.StrutStyle a = aBuilder.build(); + final ui.StrutStyle b = bBuilder.build(); + + expect(a, b); + expect(a.hashCode, b.hashCode); + }); + + test('all properties altogether not equal', () { + final _TestStrutStyleBuilder aBuilder = _TestStrutStyleBuilder(); + final _TestStrutStyleBuilder bBuilder = _TestStrutStyleBuilder(); + + for (final String property in _populatorsA.keys) { + final _StrutStylePropertyPopulator populatorA = _populatorsA[property]!; + populatorA(aBuilder); + + final _StrutStylePropertyPopulator populatorB = _populatorsB[property]!; + populatorB(bBuilder); + } + + final ui.StrutStyle a = aBuilder.build(); + final ui.StrutStyle b = bBuilder.build(); + + expect(a, isNot(b)); + expect(a.hashCode, isNot(b.hashCode)); + }); +} + +typedef _StrutStylePropertyPopulator = void Function(_TestStrutStyleBuilder builder); + +final Map _populatorsA = { + 'fontFamily': (_TestStrutStyleBuilder builder) { builder.fontFamily = 'Arial'; }, + // Intentionally do not use const List to make sure Object.hashAll is used to compute hashCode + 'fontFamilyFallback': (_TestStrutStyleBuilder builder) { builder.fontFamilyFallback = ['Roboto']; }, + 'fontSize': (_TestStrutStyleBuilder builder) { builder.fontSize = 12; }, + 'height': (_TestStrutStyleBuilder builder) { builder.height = 13; }, + 'leading': (_TestStrutStyleBuilder builder) { builder.leading = 0.1; }, + 'fontWeight': (_TestStrutStyleBuilder builder) { builder.fontWeight = ui.FontWeight.w400; }, + 'fontStyle': (_TestStrutStyleBuilder builder) { builder.fontStyle = ui.FontStyle.normal; }, + 'forceStrutHeight': (_TestStrutStyleBuilder builder) { builder.forceStrutHeight = false; }, + 'leadingDistribution': (_TestStrutStyleBuilder builder) { builder.leadingDistribution = ui.TextLeadingDistribution.proportional; }, +}; + +final Map _populatorsB = { + 'fontFamily': (_TestStrutStyleBuilder builder) { builder.fontFamily = 'Noto'; }, + // Intentionally do not use const List to make sure Object.hashAll is used to compute hashCode + 'fontFamilyFallback': (_TestStrutStyleBuilder builder) { builder.fontFamilyFallback = ['Verdana']; }, + 'fontSize': (_TestStrutStyleBuilder builder) { builder.fontSize = 12.1; }, + 'height': (_TestStrutStyleBuilder builder) { builder.height = 13.1; }, + 'leading': (_TestStrutStyleBuilder builder) { builder.leading = 0.2; }, + 'fontWeight': (_TestStrutStyleBuilder builder) { builder.fontWeight = ui.FontWeight.w600; }, + 'fontStyle': (_TestStrutStyleBuilder builder) { builder.fontStyle = ui.FontStyle.italic; }, + 'forceStrutHeight': (_TestStrutStyleBuilder builder) { builder.forceStrutHeight = true; }, + 'leadingDistribution': (_TestStrutStyleBuilder builder) { builder.leadingDistribution = ui.TextLeadingDistribution.even; }, +}; + +class _TestStrutStyleBuilder { + String? fontFamily; + List? fontFamilyFallback; + double? fontSize; + double? height; + double? leading; + ui.FontWeight? fontWeight; + ui.FontStyle? fontStyle; + bool? forceStrutHeight; + ui.TextLeadingDistribution? leadingDistribution; + + ui.StrutStyle build() { + return ui.StrutStyle( + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + height: height, + leading: leading, + fontWeight: fontWeight, + fontStyle: fontStyle, + forceStrutHeight: forceStrutHeight, + leadingDistribution: leadingDistribution, + ); + } +} diff --git a/lib/web_ui/test/ui/text_style_test.dart b/lib/web_ui/test/ui/text_style_test.dart index 60e33994c6827..2b2ba23dff4aa 100644 --- a/lib/web_ui/test/ui/text_style_test.dart +++ b/lib/web_ui/test/ui/text_style_test.dart @@ -116,6 +116,7 @@ final ui.Paint _foregroundA = ui.Paint(); final ui.Paint _backgroundB = ui.Paint(); final ui.Paint _foregroundB = ui.Paint(); +// Intentionally do not use const List expressions to make sure Object.hashAll is used to compute hashCode final Map _populatorsA = { 'color': (_TestTextStyleBuilder builder) { builder.color = const ui.Color(0xff000000); }, 'decoration': (_TestTextStyleBuilder builder) { builder.decoration = ui.TextDecoration.none; }, @@ -140,6 +141,7 @@ final Map _populatorsA = [ const ui.FontVariation.italic(0.1)]; }, }; +// Intentionally do not use const List expressions to make sure Object.hashAll is used to compute hashCode final Map _populatorsB = { 'color': (_TestTextStyleBuilder builder) { builder.color = const ui.Color(0xffbb0000); }, 'decoration': (_TestTextStyleBuilder builder) { builder.decoration = ui.TextDecoration.lineThrough; }, From 814d20c27c8c66a611792a29f10bf50ba3e20668 Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Sun, 14 Jan 2024 15:46:42 -0800 Subject: [PATCH 3/4] fix analysis errors --- lib/web_ui/lib/src/engine/canvaskit/text.dart | 7 ++++-- .../engine/skwasm/skwasm_impl/paragraph.dart | 25 +++++++++++-------- lib/web_ui/lib/src/engine/text/paragraph.dart | 7 ++++-- lib/web_ui/test/canvaskit/text_test.dart | 12 ++++----- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index e4857f9d4bcc5..64cf2838ae873 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -726,9 +726,11 @@ class CkStrutStyle implements ui.StrutStyle { } @override - int get hashCode => Object.hash( + int get hashCode { + final List? fontFamilyFallback = _fontFamilyFallback; + return Object.hash( _fontFamily, - _fontFamilyFallback != null ? Object.hashAll(_fontFamilyFallback) : null, + fontFamilyFallback != null ? Object.hashAll(fontFamilyFallback) : null, _fontSize, _height, _leading, @@ -737,6 +739,7 @@ class CkStrutStyle implements ui.StrutStyle { _fontStyle, _forceStrutHeight, ); + } } SkFontStyle toSkFontStyle(ui.FontWeight? fontWeight, ui.FontStyle? fontStyle) { diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index d107a39d2d1ca..3f75b760262c4 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -650,17 +650,20 @@ final class SkwasmStrutStyle extends SkwasmObjectWrapper implemen } @override - int get hashCode => Object.hash( - _fontFamily, - _fontFamilyFallback != null ? Object.hashAll(_fontFamilyFallback) : null, - _fontSize, - _height, - _leading, - _leadingDistribution, - _fontWeight, - _fontStyle, - _forceStrutHeight, - ); + int get hashCode { + final List? fontFamilyFallback = _fontFamilyFallback; + return Object.hash( + _fontFamily, + fontFamilyFallback != null ? Object.hashAll(fontFamilyFallback) : null, + _fontSize, + _height, + _leading, + _leadingDistribution, + _fontWeight, + _fontStyle, + _forceStrutHeight, + ); + } } class SkwasmParagraphStyle extends SkwasmObjectWrapper implements ui.ParagraphStyle { diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index 6eff331c879cf..a883f87f38552 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -846,9 +846,11 @@ class EngineStrutStyle implements ui.StrutStyle { } @override - int get hashCode => Object.hash( + int get hashCode { + final List? fontFamilyFallback = _fontFamilyFallback; + return Object.hash( _fontFamily, - _fontFamilyFallback != null ? Object.hashAll(_fontFamilyFallback) : null, + fontFamilyFallback != null ? Object.hashAll(fontFamilyFallback) : null, _fontSize, _height, _leading, @@ -857,6 +859,7 @@ class EngineStrutStyle implements ui.StrutStyle { _fontStyle, _forceStrutHeight, ); + } } /// Holds information for a placeholder in a paragraph. diff --git a/lib/web_ui/test/canvaskit/text_test.dart b/lib/web_ui/test/canvaskit/text_test.dart index e99f03f9e5953..bc0f58070125e 100644 --- a/lib/web_ui/test/canvaskit/text_test.dart +++ b/lib/web_ui/test/canvaskit/text_test.dart @@ -105,23 +105,23 @@ void testMain() { test('The default test font is used when a non-test fontFamily is specified', () { final String defaultTestFontFamily = testFonts.first; - expect(CkTextStyle(fontFamily: 'BogusFontFamily').fontFamily, defaultTestFontFamily); - expect(CkParagraphStyle(fontFamily: 'BogusFontFamily').getTextStyle().fontFamily, defaultTestFontFamily); + expect(CkTextStyle(fontFamily: 'BogusFontFamily').effectiveFontFamily, defaultTestFontFamily); + expect(CkParagraphStyle(fontFamily: 'BogusFontFamily').getTextStyle().effectiveFontFamily, defaultTestFontFamily); expect(CkStrutStyle(fontFamily: 'BogusFontFamily'), CkStrutStyle(fontFamily: defaultTestFontFamily)); }); test('The default test font is used when fontFamily is unspecified', () { final String defaultTestFontFamily = testFonts.first; - expect(CkTextStyle().fontFamily, defaultTestFontFamily); - expect(CkParagraphStyle().getTextStyle().fontFamily, defaultTestFontFamily); + expect(CkTextStyle().effectiveFontFamily, defaultTestFontFamily); + expect(CkParagraphStyle().getTextStyle().effectiveFontFamily, defaultTestFontFamily); expect(CkStrutStyle(), CkStrutStyle(fontFamily: defaultTestFontFamily)); }); test('Can specify test fontFamily to use', () { for (final String testFont in testFonts) { - expect(CkTextStyle(fontFamily: testFont).fontFamily, testFont); - expect(CkParagraphStyle(fontFamily: testFont).getTextStyle().fontFamily, testFont); + expect(CkTextStyle(fontFamily: testFont).effectiveFontFamily, testFont); + expect(CkParagraphStyle(fontFamily: testFont).getTextStyle().effectiveFontFamily, testFont); } }); }); From f862454f9fecad3a12c08f89442382dce9458fd6 Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Tue, 16 Jan 2024 15:14:20 -0800 Subject: [PATCH 4/4] fix toString in various classes --- .../src/engine/canvaskit/image_filter.dart | 15 +-- .../lib/src/engine/canvaskit/painting.dart | 79 +++++++++++ .../lib/src/engine/canvaskit/shader.dart | 9 ++ lib/web_ui/lib/src/engine/canvaskit/text.dart | 93 +++++++------ lib/web_ui/lib/src/engine/html/painting.dart | 125 +++++++++++------- .../lib/src/engine/html/shaders/shader.dart | 5 +- .../src/engine/skwasm/skwasm_impl/paint.dart | 4 + .../engine/skwasm/skwasm_impl/paragraph.dart | 56 ++++---- .../engine/skwasm/skwasm_impl/shaders.dart | 3 + lib/web_ui/lib/src/engine/text/paragraph.dart | 1 + lib/web_ui/lib/src/engine/util.dart | 14 ++ lib/web_ui/test/ui/paint_test.dart | 71 ++++++++++ lib/web_ui/test/ui/text_style_test.dart | 82 ++++++++++++ 13 files changed, 428 insertions(+), 129 deletions(-) create mode 100644 lib/web_ui/test/ui/paint_test.dart diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart index 8ce379fb1b064..19b89061144a7 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart @@ -120,19 +120,6 @@ class _CkBlurImageFilter extends CkImageFilter { borrow(_ref.nativeObject); } - String get _modeString { - switch (tileMode) { - case ui.TileMode.clamp: - return 'clamp'; - case ui.TileMode.mirror: - return 'mirror'; - case ui.TileMode.repeated: - return 'repeated'; - case ui.TileMode.decal: - return 'decal'; - } - } - @override bool operator ==(Object other) { if (runtimeType != other.runtimeType) { @@ -149,7 +136,7 @@ class _CkBlurImageFilter extends CkImageFilter { @override String toString() { - return 'ImageFilter.blur($sigmaX, $sigmaY, $_modeString)'; + return 'ImageFilter.blur($sigmaX, $sigmaY, ${tileModeString(tileMode)})'; } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/painting.dart b/lib/web_ui/lib/src/engine/canvaskit/painting.dart index b912c2e821a42..00bead7240fb6 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/painting.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/painting.dart @@ -296,6 +296,85 @@ class CkPaint implements ui.Paint { void dispose() { _ref.dispose(); } + + // Must be kept in sync with the default in paint.cc. + static const double _kStrokeMiterLimitDefault = 4.0; + + // Must be kept in sync with the default in paint.cc. + static const int _kColorDefault = 0xFF000000; + + // Must be kept in sync with the default in paint.cc. + static final int _kBlendModeDefault = ui.BlendMode.srcOver.index; + + @override + String toString() { + String resultString = 'Paint()'; + + assert(() { + final StringBuffer result = StringBuffer(); + String semicolon = ''; + result.write('Paint('); + if (style == ui.PaintingStyle.stroke) { + result.write('$style'); + if (strokeWidth != 0.0) { + result.write(' ${strokeWidth.toStringAsFixed(1)}'); + } else { + result.write(' hairline'); + } + if (strokeCap != ui.StrokeCap.butt) { + result.write(' $strokeCap'); + } + if (strokeJoin == ui.StrokeJoin.miter) { + if (strokeMiterLimit != _kStrokeMiterLimitDefault) { + result.write(' $strokeJoin up to ${strokeMiterLimit.toStringAsFixed(1)}'); + } + } else { + result.write(' $strokeJoin'); + } + semicolon = '; '; + } + if (!isAntiAlias) { + result.write('${semicolon}antialias off'); + semicolon = '; '; + } + if (color != const ui.Color(_kColorDefault)) { + result.write('$semicolon$color'); + semicolon = '; '; + } + if (blendMode.index != _kBlendModeDefault) { + result.write('$semicolon$blendMode'); + semicolon = '; '; + } + if (colorFilter != null) { + result.write('${semicolon}colorFilter: $colorFilter'); + semicolon = '; '; + } + if (maskFilter != null) { + result.write('${semicolon}maskFilter: $maskFilter'); + semicolon = '; '; + } + if (filterQuality != ui.FilterQuality.none) { + result.write('${semicolon}filterQuality: $filterQuality'); + semicolon = '; '; + } + if (shader != null) { + result.write('${semicolon}shader: $shader'); + semicolon = '; '; + } + if (imageFilter != null) { + result.write('${semicolon}imageFilter: $imageFilter'); + semicolon = '; '; + } + if (invertColors) { + result.write('${semicolon}invert: $invertColors'); + } + result.write(')'); + resultString = result.toString(); + return true; + }()); + + return resultString; + } } final Float32List _invertColorMatrix = Float32List.fromList(const [ diff --git a/lib/web_ui/lib/src/engine/canvaskit/shader.dart b/lib/web_ui/lib/src/engine/canvaskit/shader.dart index d09e507a18113..5c83adc4e32b7 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/shader.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/shader.dart @@ -54,6 +54,9 @@ abstract class SimpleCkShader implements CkShader { void dispose() { _ref.dispose(); } + + @override + String toString() => 'Gradient()'; } class CkGradientSweep extends SimpleCkShader implements ui.Gradient { @@ -133,6 +136,9 @@ class CkGradientLinear extends SimpleCkShader implements ui.Gradient { matrix4 != null ? toSkMatrixFromFloat32(matrix4!) : null, ); } + + @override + String toString() => 'Gradient()'; } class CkGradientRadial extends SimpleCkShader implements ui.Gradient { @@ -161,6 +167,9 @@ class CkGradientRadial extends SimpleCkShader implements ui.Gradient { 0, ); } + + @override + String toString() => 'Gradient()'; } class CkGradientConical extends SimpleCkShader implements ui.Gradient { diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index 64cf2838ae873..235690f2487aa 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -100,7 +100,7 @@ class CkParagraphStyle implements ui.ParagraphStyle { skTextStyle.heightMultiplier = height; } - skTextStyle.fontFamilies = _getEffectiveFontFamilies(fontFamily); + skTextStyle.fontFamilies = _computeCombinedFontFamilies(fontFamily); return skTextStyle; } @@ -110,7 +110,7 @@ class CkParagraphStyle implements ui.ParagraphStyle { final CkStrutStyle style = value as CkStrutStyle; final SkStrutStyleProperties skStrutStyle = SkStrutStyleProperties(); skStrutStyle.fontFamilies = - _getEffectiveFontFamilies(style._fontFamily, style._fontFamilyFallback); + _computeCombinedFontFamilies(style._fontFamily, style._fontFamilyFallback); if (style._fontSize != null) { skStrutStyle.fontSize = style._fontSize; @@ -222,7 +222,8 @@ class CkParagraphStyle implements ui.ParagraphStyle { decorationStyle: null, decorationThickness: null, textBaseline: null, - fontFamilyFallback: null, + originalFontFamilyFallback: null, + effectiveFontFamilyFallback: null, letterSpacing: null, wordSpacing: null, locale: null, @@ -344,7 +345,8 @@ class CkTextStyle implements ui.TextStyle { textBaseline: textBaseline, originalFontFamily: fontFamily, effectiveFontFamily: _computeEffectiveFontFamily(fontFamily), - fontFamilyFallback: ui_web.debugEmulateFlutterTesterEnvironment ? null : fontFamilyFallback, + originalFontFamilyFallback: fontFamilyFallback, + effectiveFontFamilyFallback: ui_web.debugEmulateFlutterTesterEnvironment ? null : fontFamilyFallback, fontSize: fontSize, letterSpacing: letterSpacing, wordSpacing: wordSpacing, @@ -370,7 +372,8 @@ class CkTextStyle implements ui.TextStyle { required this.textBaseline, required this.originalFontFamily, required this.effectiveFontFamily, - required this.fontFamilyFallback, + required this.originalFontFamilyFallback, + required this.effectiveFontFamilyFallback, required this.fontSize, required this.letterSpacing, required this.wordSpacing, @@ -394,7 +397,8 @@ class CkTextStyle implements ui.TextStyle { final ui.TextBaseline? textBaseline; final String? originalFontFamily; final String? effectiveFontFamily; - final List? fontFamilyFallback; + final List? originalFontFamilyFallback; + final List? effectiveFontFamilyFallback; final double? fontSize; final double? letterSpacing; final double? wordSpacing; @@ -423,7 +427,8 @@ class CkTextStyle implements ui.TextStyle { textBaseline: other.textBaseline ?? textBaseline, originalFontFamily: other.originalFontFamily ?? originalFontFamily, effectiveFontFamily: other.effectiveFontFamily ?? effectiveFontFamily, - fontFamilyFallback: other.fontFamilyFallback ?? fontFamilyFallback, + originalFontFamilyFallback: other.originalFontFamilyFallback ?? originalFontFamilyFallback, + effectiveFontFamilyFallback: other.effectiveFontFamilyFallback ?? effectiveFontFamilyFallback, fontSize: other.fontSize ?? fontSize, letterSpacing: other.letterSpacing ?? letterSpacing, wordSpacing: other.wordSpacing ?? wordSpacing, @@ -438,9 +443,9 @@ class CkTextStyle implements ui.TextStyle { ); } - /// Lazy-initialized list of font families sent to Skia. - late final List effectiveFontFamilies = - _getEffectiveFontFamilies(effectiveFontFamily, fontFamilyFallback); + /// Lazy-initialized combination of font family and font family fallback sent to Skia. + late final List combinedFontFamilies = + _computeCombinedFontFamilies(effectiveFontFamily, effectiveFontFamilyFallback); /// Lazy-initialized Skia style used to pass the style to Skia. /// @@ -536,7 +541,7 @@ class CkTextStyle implements ui.TextStyle { properties.locale = locale.toLanguageTag(); } - properties.fontFamilies = effectiveFontFamilies; + properties.fontFamilies = combinedFontFamilies; if (fontWeight != null || fontStyle != null) { properties.fontStyle = toSkFontStyle(fontWeight, fontStyle); @@ -607,7 +612,7 @@ class CkTextStyle implements ui.TextStyle { && other.background == background && other.foreground == foreground && listEquals(other.shadows, shadows) - && listEquals(other.fontFamilyFallback, fontFamilyFallback) + && listEquals(other.originalFontFamilyFallback, originalFontFamilyFallback) && listEquals(other.fontFeatures, fontFeatures) && listEquals(other.fontVariations, fontVariations); } @@ -617,7 +622,7 @@ class CkTextStyle implements ui.TextStyle { final List? shadows = this.shadows; final List? fontFeatures = this.fontFeatures; final List? fontVariations = this.fontVariations; - final List? fontFamilyFallback = this.fontFamilyFallback; + final List? fontFamilyFallback = originalFontFamilyFallback; return Object.hash( color, decoration, @@ -648,31 +653,37 @@ class CkTextStyle implements ui.TextStyle { @override String toString() { - final List? fontFamilyFallback = this.fontFamilyFallback; - final String? originalFontFamily = this.originalFontFamily; - return 'TextStyle(' - 'color: ${color ?? "unspecified"}, ' - 'decoration: ${decoration ?? "unspecified"}, ' - 'decorationColor: ${decorationColor ?? "unspecified"}, ' - 'decorationStyle: ${decorationStyle ?? "unspecified"}, ' - 'decorationThickness: ${decorationThickness ?? "unspecified"}, ' - 'fontWeight: ${fontWeight ?? "unspecified"}, ' - 'fontStyle: ${fontStyle ?? "unspecified"}, ' - 'textBaseline: ${textBaseline ?? "unspecified"}, ' - 'fontFamily: ${originalFontFamily != null && originalFontFamily.isNotEmpty ? originalFontFamily : "unspecified"}, ' - 'fontFamilyFallback: ${fontFamilyFallback != null && fontFamilyFallback.isNotEmpty ? fontFamilyFallback : "unspecified"}, ' - 'fontSize: ${fontSize ?? "unspecified"}, ' - 'letterSpacing: ${letterSpacing != null ? "${letterSpacing}x" : "unspecified"}, ' - 'wordSpacing: ${wordSpacing != null ? "${wordSpacing}x" : "unspecified"}, ' - 'height: ${height != null ? "${height}x" : "unspecified"}, ' - 'leadingDistribution: ${leadingDistribution ?? "unspecified"}, ' - 'locale: ${locale ?? "unspecified"}, ' - 'background: ${background ?? "unspecified"}, ' - 'foreground: ${foreground ?? "unspecified"}, ' - 'shadows: ${shadows ?? "unspecified"}, ' - 'fontFeatures: ${fontFeatures ?? "unspecified"}, ' - 'fontVariations: ${fontVariations ?? "unspecified"}' - ')'; + String result = super.toString(); + assert(() { + final List? fontFamilyFallback = originalFontFamilyFallback; + final double? fontSize = this.fontSize; + final double? height = this.height; + result = 'TextStyle(' + 'color: ${color ?? "unspecified"}, ' + 'decoration: ${decoration ?? "unspecified"}, ' + 'decorationColor: ${decorationColor ?? "unspecified"}, ' + 'decorationStyle: ${decorationStyle ?? "unspecified"}, ' + 'decorationThickness: ${decorationThickness ?? "unspecified"}, ' + 'fontWeight: ${fontWeight ?? "unspecified"}, ' + 'fontStyle: ${fontStyle ?? "unspecified"}, ' + 'textBaseline: ${textBaseline ?? "unspecified"}, ' + 'fontFamily: ${originalFontFamily ?? "unspecified"}, ' + 'fontFamilyFallback: ${fontFamilyFallback != null && fontFamilyFallback.isNotEmpty ? fontFamilyFallback : "unspecified"}, ' + 'fontSize: ${fontSize != null ? fontSize.toStringAsFixed(1) : "unspecified"}, ' + 'letterSpacing: ${letterSpacing != null ? "${letterSpacing}x" : "unspecified"}, ' + 'wordSpacing: ${wordSpacing != null ? "${wordSpacing}x" : "unspecified"}, ' + 'height: ${height != null ? "${height.toStringAsFixed(1)}x" : "unspecified"}, ' + 'leadingDistribution: ${leadingDistribution ?? "unspecified"}, ' + 'locale: ${locale ?? "unspecified"}, ' + 'background: ${background ?? "unspecified"}, ' + 'foreground: ${foreground ?? "unspecified"}, ' + 'shadows: ${shadows ?? "unspecified"}, ' + 'fontFeatures: ${fontFeatures ?? "unspecified"}, ' + 'fontVariations: ${fontVariations ?? "unspecified"}' + ')'; + return true; + }()); + return result; } } @@ -1107,8 +1118,8 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { if (style.effectiveFontFamily != null) { fontFamilies.add(style.effectiveFontFamily!); } - if (style.fontFamilyFallback != null) { - fontFamilies.addAll(style.fontFamilyFallback!); + if (style.effectiveFontFamilyFallback != null) { + fontFamilies.addAll(style.effectiveFontFamilyFallback!); } renderer.fontCollection.fontFallbackManager!.ensureFontsSupportText(text, fontFamilies); _paragraphBuilder.addText(text); @@ -1210,7 +1221,7 @@ class _CkParagraphPlaceholder { final double offset; } -List _getEffectiveFontFamilies(String? fontFamily, +List _computeCombinedFontFamilies(String? fontFamily, [List? fontFamilyFallback]) { final List fontFamilies = []; if (fontFamily != null) { diff --git a/lib/web_ui/lib/src/engine/html/painting.dart b/lib/web_ui/lib/src/engine/html/painting.dart index 4d44b8604a53b..dcf14e7d39600 100644 --- a/lib/web_ui/lib/src/engine/html/painting.dart +++ b/lib/web_ui/lib/src/engine/html/painting.dart @@ -96,14 +96,7 @@ class SurfacePaint implements ui.Paint { } @override - bool get invertColors { - return false; - } - - @override - set invertColors(bool value) {} - - static const int _defaultPaintColor = 0xFF000000; + bool invertColors = false; @override ui.Shader? get shader => _paintData.shader; @@ -155,25 +148,11 @@ class SurfacePaint implements ui.Paint { // TODO(ferhat): see https://github.com/flutter/flutter/issues/33605 @override - double get strokeMiterLimit { - throw UnsupportedError('SurfacePaint.strokeMiterLimit'); - } - - @override - set strokeMiterLimit(double value) { - - } - - @override - ui.ImageFilter? get imageFilter { - // TODO(ferhat): Implement ImageFilter, flutter/flutter#35156. - return null; - } + double strokeMiterLimit = 0; + // TODO(ferhat): Implement ImageFilter, flutter/flutter#35156. @override - set imageFilter(ui.ImageFilter? value) { - // TODO(ferhat): Implement ImageFilter, flutter/flutter#35156 - } + ui.ImageFilter? imageFilter; // True if Paint instance has used in RecordingCanvas. bool _frozen = false; @@ -186,33 +165,83 @@ class SurfacePaint implements ui.Paint { return _paintData; } + // Must be kept in sync with the default in paint.cc. + static const double _kStrokeMiterLimitDefault = 4.0; + + // Must be kept in sync with the default in paint.cc. + static const int _kColorDefault = 0xFF000000; + + // Must be kept in sync with the default in paint.cc. + static final int _kBlendModeDefault = ui.BlendMode.srcOver.index; + @override String toString() { - final StringBuffer result = StringBuffer(); - String semicolon = ''; - result.write('Paint('); - if (style == ui.PaintingStyle.stroke) { - result.write('$style'); - if (strokeWidth != 0.0) { - result.write(' $strokeWidth'); - } else { - result.write(' hairline'); + String resultString = 'Paint()'; + + assert(() { + final StringBuffer result = StringBuffer(); + String semicolon = ''; + result.write('Paint('); + if (style == ui.PaintingStyle.stroke) { + result.write('$style'); + if (strokeWidth != 0.0) { + result.write(' ${strokeWidth.toStringAsFixed(1)}'); + } else { + result.write(' hairline'); + } + if (strokeCap != ui.StrokeCap.butt) { + result.write(' $strokeCap'); + } + if (strokeJoin == ui.StrokeJoin.miter) { + if (strokeMiterLimit != _kStrokeMiterLimitDefault) { + result.write(' $strokeJoin up to ${strokeMiterLimit.toStringAsFixed(1)}'); + } + } else { + result.write(' $strokeJoin'); + } + semicolon = '; '; } - if (strokeCap != ui.StrokeCap.butt) { - result.write(' $strokeCap'); + if (!isAntiAlias) { + result.write('${semicolon}antialias off'); + semicolon = '; '; } - semicolon = '; '; - } - if (!isAntiAlias) { - result.write('${semicolon}antialias off'); - semicolon = '; '; - } - if (color.value != _defaultPaintColor) { - result.write('$semicolon$color'); - semicolon = '; '; - } - result.write(')'); - return result.toString(); + if (color != const ui.Color(_kColorDefault)) { + result.write('$semicolon$color'); + semicolon = '; '; + } + if (blendMode.index != _kBlendModeDefault) { + result.write('$semicolon$blendMode'); + semicolon = '; '; + } + if (colorFilter != null) { + result.write('${semicolon}colorFilter: $colorFilter'); + semicolon = '; '; + } + if (maskFilter != null) { + result.write('${semicolon}maskFilter: $maskFilter'); + semicolon = '; '; + } + if (filterQuality != ui.FilterQuality.none) { + result.write('${semicolon}filterQuality: $filterQuality'); + semicolon = '; '; + } + if (shader != null) { + result.write('${semicolon}shader: $shader'); + semicolon = '; '; + } + if (imageFilter != null) { + result.write('${semicolon}imageFilter: $imageFilter'); + semicolon = '; '; + } + if (invertColors) { + result.write('${semicolon}invert: $invertColors'); + } + result.write(')'); + resultString = result.toString(); + return true; + }()); + + return resultString; } } diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index 8faa06617c309..c484102511d51 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -63,6 +63,9 @@ abstract class EngineGradient implements ui.Gradient { @override void dispose() {} + + @override + String toString() => 'Gradient()'; } class GradientSweep extends EngineGradient { @@ -755,7 +758,7 @@ class _BlurEngineImageFilter extends EngineImageFilter { @override String toString() { - return 'ImageFilter.blur($sigmaX, $sigmaY, $tileMode)'; + return 'ImageFilter.blur($sigmaX, $sigmaY, ${tileModeString(tileMode)})'; } } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart index a813ce9483b64..26e91164134df 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart @@ -172,4 +172,8 @@ class SkwasmPaint extends SkwasmObjectWrapper implements ui.Paint { _invertColors = invertColors; _setEffectiveColorFilter(); } + + // TODO(yjbanov): https://github.com/flutter/flutter/issues/141639 + @override + String toString() => 'Paint()'; } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index 3f75b760262c4..f719326895f06 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -521,31 +521,37 @@ class SkwasmTextStyle implements ui.TextStyle { @override String toString() { - final List? fontFamilyFallback = this.fontFamilyFallback; - final String? fontFamily = this.fontFamily; - return 'TextStyle(' - 'color: ${color ?? "unspecified"}, ' - 'decoration: ${decoration ?? "unspecified"}, ' - 'decorationColor: ${decorationColor ?? "unspecified"}, ' - 'decorationStyle: ${decorationStyle ?? "unspecified"}, ' - 'decorationThickness: ${decorationThickness ?? "unspecified"}, ' - 'fontWeight: ${fontWeight ?? "unspecified"}, ' - 'fontStyle: ${fontStyle ?? "unspecified"}, ' - 'textBaseline: ${textBaseline ?? "unspecified"}, ' - 'fontFamily: ${fontFamily != null && fontFamily.isNotEmpty ? fontFamily : "unspecified"}, ' - 'fontFamilyFallback: ${fontFamilyFallback != null && fontFamilyFallback.isNotEmpty ? fontFamilyFallback : "unspecified"}, ' - 'fontSize: ${fontSize ?? "unspecified"}, ' - 'letterSpacing: ${letterSpacing != null ? "${letterSpacing}x" : "unspecified"}, ' - 'wordSpacing: ${wordSpacing != null ? "${wordSpacing}x" : "unspecified"}, ' - 'height: ${height != null ? "${height}x" : "unspecified"}, ' - 'leadingDistribution: ${leadingDistribution ?? "unspecified"}, ' - 'locale: ${locale ?? "unspecified"}, ' - 'background: ${background ?? "unspecified"}, ' - 'foreground: ${foreground ?? "unspecified"}, ' - 'shadows: ${shadows ?? "unspecified"}, ' - 'fontFeatures: ${fontFeatures ?? "unspecified"}, ' - 'fontVariations: ${fontVariations ?? "unspecified"}' - ')'; + String result = super.toString(); + assert(() { + final List? fontFamilyFallback = this.fontFamilyFallback; + final double? fontSize = this.fontSize; + final double? height = this.height; + result = 'TextStyle(' + 'color: ${color ?? "unspecified"}, ' + 'decoration: ${decoration ?? "unspecified"}, ' + 'decorationColor: ${decorationColor ?? "unspecified"}, ' + 'decorationStyle: ${decorationStyle ?? "unspecified"}, ' + 'decorationThickness: ${decorationThickness ?? "unspecified"}, ' + 'fontWeight: ${fontWeight ?? "unspecified"}, ' + 'fontStyle: ${fontStyle ?? "unspecified"}, ' + 'textBaseline: ${textBaseline ?? "unspecified"}, ' + 'fontFamily: ${fontFamily ?? "unspecified"}, ' + 'fontFamilyFallback: ${fontFamilyFallback != null && fontFamilyFallback.isNotEmpty ? fontFamilyFallback : "unspecified"}, ' + 'fontSize: ${fontSize != null ? fontSize.toStringAsFixed(1) : "unspecified"}, ' + 'letterSpacing: ${letterSpacing != null ? "${letterSpacing}x" : "unspecified"}, ' + 'wordSpacing: ${wordSpacing != null ? "${wordSpacing}x" : "unspecified"}, ' + 'height: ${height != null ? "${height.toStringAsFixed(1)}x" : "unspecified"}, ' + 'leadingDistribution: ${leadingDistribution ?? "unspecified"}, ' + 'locale: ${locale ?? "unspecified"}, ' + 'background: ${background ?? "unspecified"}, ' + 'foreground: ${foreground ?? "unspecified"}, ' + 'shadows: ${shadows ?? "unspecified"}, ' + 'fontFeatures: ${fontFeatures ?? "unspecified"}, ' + 'fontVariations: ${fontVariations ?? "unspecified"}' + ')'; + return true; + }()); + return result; } } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart index e4eb5f1935dc3..20a2b4d17772b 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart @@ -144,6 +144,9 @@ class SkwasmGradient extends SkwasmNativeShader implements ui.Gradient { }); SkwasmGradient._(super.handle); + + @override + String toString() => 'Gradient()'; } class SkwasmImageShader extends SkwasmNativeShader implements ui.ImageShader { diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index a883f87f38552..ab5b87f34e7ae 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -780,6 +780,7 @@ class EngineTextStyle implements ui.TextStyle { 'letterSpacing: ${letterSpacing != null ? "${letterSpacing}x" : "unspecified"}, ' 'wordSpacing: ${wordSpacing != null ? "${wordSpacing}x" : "unspecified"}, ' 'height: ${height != null ? "${height.toStringAsFixed(1)}x" : "unspecified"}, ' + 'leadingDistribution: ${leadingDistribution ?? "unspecified"}, ' 'locale: ${locale ?? "unspecified"}, ' 'background: ${background ?? "unspecified"}, ' 'foreground: ${foreground ?? "unspecified"}, ' diff --git a/lib/web_ui/lib/src/engine/util.dart b/lib/web_ui/lib/src/engine/util.dart index d6581459b60c2..5f54070c155f8 100644 --- a/lib/web_ui/lib/src/engine/util.dart +++ b/lib/web_ui/lib/src/engine/util.dart @@ -854,3 +854,17 @@ class LruCache { _itemQueue.removeLast(); } } + +/// Returns the VM-compatible string for the tile mode. +String tileModeString(ui.TileMode tileMode) { + switch (tileMode) { + case ui.TileMode.clamp: + return 'clamp'; + case ui.TileMode.mirror: + return 'mirror'; + case ui.TileMode.repeated: + return 'repeated'; + case ui.TileMode.decal: + return 'decal'; + } +} diff --git a/lib/web_ui/test/ui/paint_test.dart b/lib/web_ui/test/ui/paint_test.dart new file mode 100644 index 0000000000000..4518d08c4d3ec --- /dev/null +++ b/lib/web_ui/test/ui/paint_test.dart @@ -0,0 +1,71 @@ +// 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. + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/ui.dart' as ui; + +import '../common/test_initialization.dart'; +import 'utils.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +Future testMain() async { + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); + + test('toString()', () { + final ui.Paint paint = ui.Paint(); + paint.blendMode = ui.BlendMode.darken; + paint.style = ui.PaintingStyle.fill; + paint.strokeWidth = 1.2; + paint.strokeCap = ui.StrokeCap.square; + paint.strokeJoin = ui.StrokeJoin.bevel; + paint.isAntiAlias = true; + paint.color = const ui.Color(0xaabbccdd); + paint.invertColors = true; + paint.shader = ui.Gradient.linear( + const ui.Offset(0.1, 0.2), + const ui.Offset(1.5, 1.6), + const [ + ui.Color(0xaabbccdd), + ui.Color(0xbbccddee), + ], + [0.3, 0.4], + ui.TileMode.decal, + ); + paint.maskFilter = const ui.MaskFilter.blur(ui.BlurStyle.normal, 1.7); + paint.filterQuality = ui.FilterQuality.high; + paint.colorFilter = const ui.ColorFilter.linearToSrgbGamma(); + paint.strokeMiterLimit = 1.8; + paint.imageFilter = ui.ImageFilter.blur( + sigmaX: 1.9, + sigmaY: 2.1, + tileMode: ui.TileMode.mirror, + ); + + if (!isSkwasm) { + expect( + paint.toString(), + 'Paint(' + 'Color(0xaabbccdd); ' + 'BlendMode.darken; ' + 'colorFilter: ColorFilter.linearToSrgbGamma(); ' + 'maskFilter: MaskFilter.blur(BlurStyle.normal, 1.7); ' + 'filterQuality: FilterQuality.high; ' + 'shader: Gradient(); ' + 'imageFilter: ImageFilter.blur(1.9, 2.1, mirror); ' + 'invert: true' + ')', + ); + } else { + // TODO(yjbanov): https://github.com/flutter/flutter/issues/141639 + expect(paint.toString(), 'Paint()'); + } + }); +} diff --git a/lib/web_ui/test/ui/text_style_test.dart b/lib/web_ui/test/ui/text_style_test.dart index 2b2ba23dff4aa..67eda1dedf007 100644 --- a/lib/web_ui/test/ui/text_style_test.dart +++ b/lib/web_ui/test/ui/text_style_test.dart @@ -105,6 +105,88 @@ Future testMain() async { expect(a.hashCode, isNot(b.hashCode)); }); } + + test('toString() with color', () { + final _TestTextStyleBuilder builder = _TestTextStyleBuilder(); + + for (final String property in _populatorsA.keys) { + if (property == 'foreground') { + continue; + } + final _TextStylePropertyPopulator populator = _populatorsA[property]!; + populator(builder); + } + + final ui.TextStyle style = builder.build(); + + expect( + style.toString(), + 'TextStyle(' + 'color: Color(0xff000000), ' + 'decoration: TextDecoration.none, ' + 'decorationColor: Color(0xffaa0000), ' + 'decorationStyle: TextDecorationStyle.solid, ' + 'decorationThickness: ${1.0}, ' + 'fontWeight: FontWeight.w400, ' + 'fontStyle: FontStyle.normal, ' + 'textBaseline: TextBaseline.alphabetic, ' + 'fontFamily: Arial, ' + 'fontFamilyFallback: [Roboto], ' + 'fontSize: 12.0, ' + 'letterSpacing: 1.2x, ' + 'wordSpacing: 2.3x, ' + 'height: 13.0x, ' + 'leadingDistribution: TextLeadingDistribution.proportional, ' + 'locale: en_US, ' + 'background: Paint(), ' + 'foreground: unspecified, ' + 'shadows: [TextShadow(Color(0xff000000), Offset(0.0, 0.0), ${0.0})], ' + "fontFeatures: [FontFeature('case', 1)], " + "fontVariations: [FontVariation('ital', 0.1)]" + ')', + ); + }); + + test('toString() with foreground', () { + final _TestTextStyleBuilder builder = _TestTextStyleBuilder(); + + for (final String property in _populatorsA.keys) { + if (property == 'color') { + continue; + } + final _TextStylePropertyPopulator populator = _populatorsA[property]!; + populator(builder); + } + + final ui.TextStyle style = builder.build(); + + expect( + style.toString(), + 'TextStyle(' + 'color: unspecified, ' + 'decoration: TextDecoration.none, ' + 'decorationColor: Color(0xffaa0000), ' + 'decorationStyle: TextDecorationStyle.solid, ' + 'decorationThickness: ${1.0}, ' + 'fontWeight: FontWeight.w400, ' + 'fontStyle: FontStyle.normal, ' + 'textBaseline: TextBaseline.alphabetic, ' + 'fontFamily: Arial, ' + 'fontFamilyFallback: [Roboto], ' + 'fontSize: 12.0, ' + 'letterSpacing: 1.2x, ' + 'wordSpacing: 2.3x, ' + 'height: 13.0x, ' + 'leadingDistribution: TextLeadingDistribution.proportional, ' + 'locale: en_US, ' + 'background: Paint(), ' + 'foreground: Paint(), ' + 'shadows: [TextShadow(Color(0xff000000), Offset(0.0, 0.0), ${0.0})], ' + "fontFeatures: [FontFeature('case', 1)], " + "fontVariations: [FontVariation('ital', 0.1)]" + ')', + ); + }); } typedef _TextStylePropertyPopulator = void Function(_TestTextStyleBuilder builder);