未验证 提交 679a4369 编写于 作者: F Ferhat 提交者: GitHub

[web] Implement TextStyle.shadows (#13769)

* Add shadows to Engine classes
* add text shadow test
* update golden locks file, update ui.ParagraphStyle, fix issues
* Change maxDiffRate for mac clients
上级 e19ee72f
......@@ -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,
......@@ -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;
......@@ -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;
......@@ -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);
}
......@@ -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,
......@@ -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;
......@@ -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) {
......@@ -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();
......@@ -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;
......@@ -852,6 +861,9 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder {
if (style._foreground != null) {
foreground = style._foreground;
}
if (style._shadows != null) {
shadows = style._shadows;
}
i++;
}
......@@ -871,6 +883,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder {
locale: locale,
background: background,
foreground: foreground,
shadows: shadows,
);
ui.Paint paint;
......@@ -900,6 +913,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder {
wordSpacing: wordSpacing,
decoration: _textDecorationToCssString(decoration, decorationStyle),
ellipsis: _paragraphStyle._ellipsis,
shadows: shadows,
),
plainText: '',
paint: paint,
......@@ -953,6 +967,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder {
wordSpacing: wordSpacing,
decoration: _textDecorationToCssString(decoration, decorationStyle),
ellipsis: _paragraphStyle._ellipsis,
shadows: shadows,
),
plainText: plainText,
paint: paint,
......@@ -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,
......@@ -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(
......@@ -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);
}
}
}
......@@ -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) {
......@@ -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) {
......@@ -1197,6 +1223,9 @@ void _applyTextStyleToElement({
style._decorationColor != previousStyle._decorationColor) {
updateDecoration = true;
}
if (style._shadows != previousStyle._shadows) {
cssStyle.textShadow = _shadowListToCss(style._shadows);
}
}
if (updateDecoration) {
......@@ -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({
......
......@@ -17,6 +17,7 @@ class ParagraphGeometricStyle {
this.wordSpacing,
this.decoration,
this.ellipsis,
this.shadows,
});
final ui.FontWeight fontWeight;
......@@ -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.
......@@ -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
......@@ -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) {
......@@ -137,6 +144,7 @@ class ParagraphGeometricStyle {
' wordSpacing: $wordSpacing,'
' decoration: $decoration,'
' ellipsis: $ellipsis,'
' shadows: $shadows,'
')';
} else {
return super.toString();
......@@ -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();
}
......@@ -765,7 +777,7 @@ class ParagraphRuler {
return null;
}
final List<MeasurementResult> constraintCache =
_measurementCache[plainText];
_measurementCache[plainText];
if (constraintCache == null) {
return null;
}
......
......@@ -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.
......@@ -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)';
......
......@@ -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 {
......@@ -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.
......@@ -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 {
......@@ -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(
......
......@@ -158,8 +158,64 @@ void main() async {
);
}
void drawTextWithShadow(EngineCanvas canvas) {
// Single-line text.
canvas.drawParagraph(
paragraph(
'Hello World',
maxWidth: 600,
textStyle: TextStyle(
color: const Color.fromRGBO(0, 0, 0, 1.0),
background: Paint()..color = const Color.fromRGBO(255, 50, 50, 1.0),
fontFamily: 'Arial',
fontSize: 30,
shadows: <Shadow>[
Shadow(
blurRadius: 0,
color: const Color.fromRGBO(255, 0, 255, 1.0),
offset: Offset(10, 5),
),
],
),
),
Offset.zero,
);
// Multi-line text.
canvas.drawParagraph(
paragraph(
'Multi line Hello World paragraph',
maxWidth: 200,
textStyle: TextStyle(
color: const Color.fromRGBO(0, 0, 0, 1.0),
background: Paint()..color = const Color.fromRGBO(50, 50, 255, 1.0),
fontFamily: 'Arial',
fontSize: 30,
shadows: <Shadow>[
Shadow(
blurRadius: 0,
color: const Color.fromRGBO(255, 0, 255, 1.0),
offset: Offset(10, 5),
),
Shadow(
blurRadius: 0,
color: const Color.fromRGBO(0, 255, 255, 1.0),
offset: Offset(-10, -5),
),
],
),
),
const Offset(0, 40),
);
}
testEachCanvas('draws text with a background', (EngineCanvas canvas) {
drawTextWithBackground(canvas);
return scuba.diffCanvasScreenshot(canvas, 'text_background');
});
testEachCanvas('draws text with a shadow', (EngineCanvas canvas) {
drawTextWithShadow(canvas);
return scuba.diffCanvasScreenshot(canvas, 'text_shadow', maxDiffRate: 0.2);
}, bSkipHoudini: true);
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册