Skip to content

Commit

Permalink
[web] Implement TextStyle.shadows (flutter#13769)
Browse files Browse the repository at this point in the history
* Add shadows to Engine classes
* add text shadow test
* update golden locks file, update ui.ParagraphStyle, fix issues
* Change maxDiffRate for mac clients
  • Loading branch information
ferhatb committed Nov 12, 2019
1 parent e19ee72 commit 679a436
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 32 deletions.
58 changes: 54 additions & 4 deletions lib/web_ui/lib/src/engine/text/paragraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class EngineParagraph implements ui.Paragraph {
@required ui.TextAlign textAlign,
@required ui.TextDirection textDirection,
@required ui.Paint background,
@required List<ui.Shadow> shadows,
}) : assert((plainText == null && paint == null) ||
(plainText != null && paint != null)),
_paragraphElement = paragraphElement,
Expand All @@ -26,7 +27,8 @@ class EngineParagraph implements ui.Paragraph {
_textAlign = textAlign,
_textDirection = textDirection,
_paint = paint,
_background = background;
_background = background,
_shadows = shadows;

final html.HtmlElement _paragraphElement;
final ParagraphGeometricStyle _geometricStyle;
Expand All @@ -35,6 +37,7 @@ class EngineParagraph implements ui.Paragraph {
final ui.TextAlign _textAlign;
final ui.TextDirection _textDirection;
final ui.Paint _background;
final List<ui.Shadow> _shadows;

@visibleForTesting
String get plainText => _plainText;
Expand Down Expand Up @@ -287,7 +290,8 @@ class EngineParagraph implements ui.Paragraph {
return ui.TextRange(start: textPosition.offset, end: textPosition.offset);
}

final int start = WordBreaker.prevBreakIndex(_plainText, textPosition.offset);
final int start =
WordBreaker.prevBreakIndex(_plainText, textPosition.offset);
final int end = WordBreaker.nextBreakIndex(_plainText, textPosition.offset);
return ui.TextRange(start: start, end: end);
}
Expand Down Expand Up @@ -321,6 +325,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle {
ui.StrutStyle strutStyle,
String ellipsis,
ui.Locale locale,
List<ui.Shadow> shadows,
}) : _textAlign = textAlign,
_textDirection = textDirection,
_fontWeight = fontWeight,
Expand All @@ -332,7 +337,8 @@ class EngineParagraphStyle implements ui.ParagraphStyle {
// TODO(b/128317744): add support for strut style.
_strutStyle = strutStyle,
_ellipsis = ellipsis,
_locale = locale;
_locale = locale,
_shadows = shadows;

final ui.TextAlign _textAlign;
final ui.TextDirection _textDirection;
Expand All @@ -345,6 +351,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle {
final EngineStrutStyle _strutStyle;
final String _ellipsis;
final ui.Locale _locale;
final List<ui.Shadow> _shadows;

String get _effectiveFontFamily {
if (assertionsEnabled) {
Expand Down Expand Up @@ -413,6 +420,7 @@ class EngineParagraphStyle implements ui.ParagraphStyle {
'height: ${_height != null ? "${_height.toStringAsFixed(1)}x" : "unspecified"}, '
'ellipsis: ${_ellipsis != null ? "\"$_ellipsis\"" : "unspecified"}, '
'locale: ${_locale ?? "unspecified"}'
'shadows: ${_shadows ?? "unspecified"}'
')';
} else {
return super.toString();
Expand Down Expand Up @@ -798,6 +806,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder {
ui.Locale locale = _paragraphStyle._locale;
ui.Paint background;
ui.Paint foreground;
List<ui.Shadow> shadows;

int i = 0;

Expand Down Expand Up @@ -852,6 +861,9 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder {
if (style._foreground != null) {
foreground = style._foreground;
}
if (style._shadows != null) {
shadows = style._shadows;
}
i++;
}

Expand All @@ -871,6 +883,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder {
locale: locale,
background: background,
foreground: foreground,
shadows: shadows,
);

ui.Paint paint;
Expand Down Expand Up @@ -900,6 +913,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder {
wordSpacing: wordSpacing,
decoration: _textDecorationToCssString(decoration, decorationStyle),
ellipsis: _paragraphStyle._ellipsis,
shadows: shadows,
),
plainText: '',
paint: paint,
Expand Down Expand Up @@ -953,6 +967,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder {
wordSpacing: wordSpacing,
decoration: _textDecorationToCssString(decoration, decorationStyle),
ellipsis: _paragraphStyle._ellipsis,
shadows: shadows,
),
plainText: plainText,
paint: paint,
Expand Down Expand Up @@ -996,6 +1011,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder {
lineHeight: _paragraphStyle._height,
maxLines: _paragraphStyle._maxLines,
ellipsis: _paragraphStyle._ellipsis,
shadows: _paragraphStyle._shadows,
),
plainText: null,
paint: null,
Expand Down Expand Up @@ -1082,6 +1098,9 @@ void _applyParagraphStyleToElement({
if (style._effectiveFontFamily != null) {
cssStyle.fontFamily = canonicalizeFontFamily(style._effectiveFontFamily);
}
if (style._shadows != null) {
cssStyle.textShadow = _shadowListToCss(style._shadows);
}
} else {
if (style._textAlign != previousStyle._textAlign) {
cssStyle.textAlign = textAlignToCssValue(
Expand All @@ -1108,6 +1127,9 @@ void _applyParagraphStyleToElement({
if (style._fontFamily != previousStyle._fontFamily) {
cssStyle.fontFamily = canonicalizeFontFamily(style._fontFamily);
}
if (style._shadows != previousStyle._shadows) {
cssStyle.textShadow = _shadowListToCss(style._shadows);
}
}
}

Expand Down Expand Up @@ -1150,7 +1172,8 @@ void _applyTextStyleToElement({
}
} else {
if (style._effectiveFontFamily != null) {
cssStyle.fontFamily = canonicalizeFontFamily(style._effectiveFontFamily);
cssStyle.fontFamily =
canonicalizeFontFamily(style._effectiveFontFamily);
}
}
if (style._letterSpacing != null) {
Expand All @@ -1162,6 +1185,9 @@ void _applyTextStyleToElement({
if (style._decoration != null) {
updateDecoration = true;
}
if (style._shadows != null) {
cssStyle.textShadow = _shadowListToCss(style._shadows);
}
} else {
if (style._color != previousStyle._color ||
style._foreground != previousStyle._foreground) {
Expand Down Expand Up @@ -1197,6 +1223,9 @@ void _applyTextStyleToElement({
style._decorationColor != previousStyle._decorationColor) {
updateDecoration = true;
}
if (style._shadows != previousStyle._shadows) {
cssStyle.textShadow = _shadowListToCss(style._shadows);
}
}

if (updateDecoration) {
Expand All @@ -1214,6 +1243,27 @@ void _applyTextStyleToElement({
}
}

String _shadowListToCss(List<ui.Shadow> shadows) {
if (shadows.isEmpty) {
return '';
}
// CSS text-shadow is a comma separated list of shadows.
// <offsetx> <offsety> <blur-radius> <color>.
// Shadows are applied front-to-back with first shadow on top.
// Color is optional. offsetx,y are required. blur-radius is optional as well
// and defaults to 0.
StringBuffer sb = new StringBuffer();
for (int i = 0, len = shadows.length; i < len; i++) {
if (i != 0) {
sb.write(',');
}
ui.Shadow shadow = shadows[i];
sb.write('${shadow.offset.dx}px ${shadow.offset.dy}px '
'${shadow.blurRadius}px ${shadow.color.toCssString()}');
}
return sb.toString();
}

/// Applies background color properties in text style to paragraph or span
/// elements.
void _applyTextBackgroundToElement({
Expand Down
16 changes: 14 additions & 2 deletions lib/web_ui/lib/src/engine/text/ruler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class ParagraphGeometricStyle {
this.wordSpacing,
this.decoration,
this.ellipsis,
this.shadows,
});

final ui.FontWeight fontWeight;
Expand All @@ -29,6 +30,7 @@ class ParagraphGeometricStyle {
final double wordSpacing;
final String decoration;
final String ellipsis;
final List<ui.Shadow> shadows;

// Since all fields above are primitives, cache hashcode since ruler lookups
// use this style as key.
Expand Down Expand Up @@ -109,7 +111,8 @@ class ParagraphGeometricStyle {
letterSpacing == typedOther.letterSpacing &&
wordSpacing == typedOther.wordSpacing &&
decoration == typedOther.decoration &&
ellipsis == typedOther.ellipsis;
ellipsis == typedOther.ellipsis &&
shadows == typedOther.shadows;
}

@override
Expand All @@ -124,8 +127,12 @@ class ParagraphGeometricStyle {
wordSpacing,
decoration,
ellipsis,
_hashShadows(shadows),
);

int _hashShadows(List<ui.Shadow> shadows) =>
(shadows == null ? '' : _shadowListToCss(shadows)).hashCode;

@override
String toString() {
if (assertionsEnabled) {
Expand All @@ -137,6 +144,7 @@ class ParagraphGeometricStyle {
' wordSpacing: $wordSpacing,'
' decoration: $decoration,'
' ellipsis: $ellipsis,'
' shadows: $shadows,'
')';
} else {
return super.toString();
Expand Down Expand Up @@ -241,6 +249,10 @@ class TextDimensions {
if (style.lineHeight != null) {
_element.style.lineHeight = style.lineHeight.toString();
}
final List<ui.Shadow> shadowList = style.shadows;
if (shadowList != null) {
_element.style.textShadow = _shadowListToCss(shadowList);
}
_invalidateBoundsCache();
}

Expand Down Expand Up @@ -765,7 +777,7 @@ class ParagraphRuler {
return null;
}
final List<MeasurementResult> constraintCache =
_measurementCache[plainText];
_measurementCache[plainText];
if (constraintCache == null) {
return null;
}
Expand Down
17 changes: 7 additions & 10 deletions lib/web_ui/lib/src/ui/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,7 @@ class TextRange {
const TextRange({
this.start,
this.end,
}) : assert(start != null && start >= -1),
}) : assert(start != null && start >= -1),
assert(end != null && end >= -1);

/// A text range that starts and ends at offset.
Expand Down Expand Up @@ -971,20 +971,17 @@ class TextRange {

@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! TextRange)
return false;
if (identical(this, other)) return true;
if (other is! TextRange) return false;
final TextRange typedOther = other;
return typedOther.start == start
&& typedOther.end == end;
return typedOther.start == start && typedOther.end == end;
}

@override
int get hashCode => hashValues(
start.hashCode,
end.hashCode,
);
start.hashCode,
end.hashCode,
);

@override
String toString() => 'TextRange(start: $start, end: $end)';
Expand Down
35 changes: 19 additions & 16 deletions lib/web_ui/test/golden_tests/engine/scuba.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,18 @@ class EngineScubaTester {
return EngineScubaTester(viewportSize);
}

Future<void> diffScreenshot(String fileName) async {
await matchGoldenFile('$fileName.png', region: ui.Rect.fromLTWH(0, 0, viewportSize.width, viewportSize.height));
Future<void> diffScreenshot(String fileName, {double maxDiffRate}) async {
await matchGoldenFile('$fileName.png',
region: ui.Rect.fromLTWH(0, 0, viewportSize.width, viewportSize.height),
maxDiffRate: maxDiffRate);
}

/// Prepares the DOM and inserts all the necessary nodes, then invokes scuba's
/// screenshot diffing.
///
/// It also cleans up the DOM after itself.
Future<void> diffCanvasScreenshot(
EngineCanvas canvas,
String fileName,
) async {
Future<void> diffCanvasScreenshot(EngineCanvas canvas, String fileName,
{double maxDiffRate}) async {
// Wrap in <flt-scene> so that our CSS selectors kick in.
final html.Element sceneElement = html.Element.tag('flt-scene');
try {
Expand All @@ -60,7 +60,7 @@ class EngineScubaTester {
if (TextMeasurementService.enableExperimentalCanvasImplementation) {
screenshotName += '+canvas_measurement';
}
await diffScreenshot(screenshotName);
await diffScreenshot(screenshotName, maxDiffRate: maxDiffRate);
} finally {
// The page is reused across tests, so remove the element after taking the
// Scuba screenshot.
Expand All @@ -72,7 +72,8 @@ class EngineScubaTester {
typedef CanvasTest = FutureOr<void> Function(EngineCanvas canvas);

/// Runs the given test [body] with each type of canvas.
void testEachCanvas(String description, CanvasTest body) {
void testEachCanvas(String description, CanvasTest body,
{double maxDiffRate, bool bSkipHoudini = false}) {
const ui.Rect bounds = ui.Rect.fromLTWH(0, 0, 600, 800);
test('$description (bitmap)', () {
try {
Expand Down Expand Up @@ -100,14 +101,16 @@ void testEachCanvas(String description, CanvasTest body) {
TextMeasurementService.clearCache();
}
});
test('$description (houdini)', () {
try {
TextMeasurementService.initialize(rulerCacheCapacity: 2);
return body(HoudiniCanvas(bounds));
} finally {
TextMeasurementService.clearCache();
}
});
if (!bSkipHoudini) {
test('$description (houdini)', () {
try {
TextMeasurementService.initialize(rulerCacheCapacity: 2);
return body(HoudiniCanvas(bounds));
} finally {
TextMeasurementService.clearCache();
}
});
}
}

final ui.TextStyle _defaultTextStyle = ui.TextStyle(
Expand Down

0 comments on commit 679a436

Please sign in to comment.