[web] Use greatest span font size on the parent paragraph (#25513)
diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml
index d081079..f04236f 100644
--- a/lib/web_ui/dev/goldens_lock.yaml
+++ b/lib/web_ui/dev/goldens_lock.yaml
@@ -1,2 +1,2 @@
repository: https://github.com/flutter/goldens.git
-revision: 82c1ccb1247f279cfd9f23a3929d13e2f32771ad
+revision: f868ee6b6faef13469498e9b160b45fed9fdc567
diff --git a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart
index ce9e131..2f626de 100644
--- a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart
+++ b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart
@@ -134,6 +134,7 @@
// 1. Set paragraph-level styles.
_applyNecessaryParagraphStyles(element: rootElement, style: paragraphStyle);
+ _applySpanStylesToParagraph(element: rootElement, spans: spans);
final html.CssStyleDeclaration cssStyle = rootElement.style;
cssStyle
..position = 'absolute'
@@ -284,6 +285,39 @@
}
}
+/// Applies some span-level style to a paragraph [element].
+///
+/// For example, it looks for the greatest font size among spans, and applies it
+/// to the paragraph. While this seems to have no effect, it prevents the
+/// paragraph from inheriting its font size from the body tag, which leads to
+/// incorrect vertical alignment of spans.
+void _applySpanStylesToParagraph({
+ required html.HtmlElement element,
+ required List<ParagraphSpan> spans,
+}) {
+ double fontSize = 0.0;
+ String? fontFamily;
+ for (final ParagraphSpan span in spans) {
+ if (span is FlatTextSpan) {
+ final double? spanFontSize = span.style._fontSize;
+ if (spanFontSize != null && spanFontSize > fontSize) {
+ fontSize = spanFontSize;
+ if (span.style._isFontFamilyProvided) {
+ fontFamily = span.style._effectiveFontFamily;
+ }
+ }
+ }
+ }
+
+ final html.CssStyleDeclaration cssStyle = element.style;
+ if (fontSize != 0.0) {
+ cssStyle.fontSize = '${fontSize}px';
+ }
+ if (fontFamily != null) {
+ cssStyle.fontFamily = canonicalizeFontFamily(fontFamily);
+ }
+}
+
/// A common interface for all types of spans that make up a paragraph.
///
/// These spans are stored as a flat list in the paragraph object.
diff --git a/lib/web_ui/test/golden_tests/engine/canvas_paragraph/general_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_paragraph/general_test.dart
index 73a97de..d86a7fd 100644
--- a/lib/web_ui/test/golden_tests/engine/canvas_paragraph/general_test.dart
+++ b/lib/web_ui/test/golden_tests/engine/canvas_paragraph/general_test.dart
@@ -4,6 +4,7 @@
// @dart = 2.6
import 'dart:async';
+import 'dart:html' as html;
import 'dart:math' as math;
import 'package:test/bootstrap/browser.dart';
@@ -226,6 +227,63 @@
return takeScreenshot(canvas, bounds, 'canvas_paragraph_giant_paragraph_style_dom');
});
+ test('giant font size on the body tag (DOM)', () async {
+ const Rect bounds = Rect.fromLTWH(0, 0, 600, 200);
+
+ // Store the old font size value on the body, and set a gaint font size.
+ final String oldBodyFontSize = html.document.body.style.fontSize;
+ html.document.body.style.fontSize = '100px';
+
+ final canvas = DomCanvas(domRenderer.createElement('flt-picture'));
+ Offset offset = Offset(10.0, 10.0);
+
+ final CanvasParagraph paragraph = rich(
+ ParagraphStyle(fontFamily: 'Roboto'),
+ (CanvasParagraphBuilder builder) {
+ builder.pushStyle(EngineTextStyle.only(color: yellow, fontSize: 24.0));
+ builder.addText('Lorem ');
+ builder.pushStyle(EngineTextStyle.only(color: red, fontSize: 48.0));
+ builder.addText('ipsum');
+ },
+ )..layout(constrain(double.infinity));
+ final Rect rect = Rect.fromLTWH(offset.dx, offset.dy, paragraph.maxIntrinsicWidth, paragraph.height);
+ canvas.drawRect(rect, SurfacePaintData()..color = black);
+ canvas.drawParagraph(paragraph, offset);
+ offset = offset.translate(paragraph.maxIntrinsicWidth, 0.0);
+
+ // Add some extra padding between the two paragraphs.
+ offset = offset.translate(20.0, 0.0);
+
+ // Use the same height as the previous paragraph so that the 2 paragraphs
+ // look nice in the screenshot.
+ final double placeholderHeight = paragraph.height;
+ final double placeholderWidth = paragraph.height * 2;
+
+ final CanvasParagraph paragraph2 = rich(
+ ParagraphStyle(),
+ (CanvasParagraphBuilder builder) {
+ builder.addPlaceholder(placeholderWidth, placeholderHeight, PlaceholderAlignment.baseline, baseline: TextBaseline.alphabetic);
+ },
+ )..layout(constrain(double.infinity));
+ final Rect rect2 = Rect.fromLTWH(offset.dx, offset.dy, paragraph2.maxIntrinsicWidth, paragraph2.height);
+ canvas.drawRect(rect2, SurfacePaintData()..color = black);
+ canvas.drawParagraph(paragraph2, offset);
+ // Draw a rect in the placeholder.
+ // Leave some padding around the placeholder to make the black paragraph
+ // background visible.
+ final double padding = 5;
+ final TextBox placeholderBox = paragraph2.getBoxesForPlaceholders().single;
+ canvas.drawRect(
+ placeholderBox.toRect().shift(offset).deflate(padding),
+ SurfacePaintData()..color = red,
+ );
+
+ await takeScreenshot(canvas, bounds, 'canvas_paragraph_giant_body_font_size_dom');
+
+ // Restore the old font size value.
+ html.document.body.style.fontSize = oldBodyFontSize;
+ });
+
test('paints spans with varying heights/baselines', () {
final canvas = BitmapCanvas(bounds, RenderStrategy());
diff --git a/lib/web_ui/test/text/canvas_paragraph_builder_test.dart b/lib/web_ui/test/text/canvas_paragraph_builder_test.dart
index 3cd1ff5..8c48e36 100644
--- a/lib/web_ui/test/text/canvas_paragraph_builder_test.dart
+++ b/lib/web_ui/test/text/canvas_paragraph_builder_test.dart
@@ -11,8 +11,8 @@
bool get isIosSafari => browserEngine == BrowserEngine.webkit &&
operatingSystem == OperatingSystem.iOs;
-String get defaultFontFamily {
- String fontFamily = canonicalizeFontFamily('Ahem')!;
+String fontFamilyToAttribute(String fontFamily) {
+ fontFamily = canonicalizeFontFamily(fontFamily)!;
if (browserEngine == BrowserEngine.firefox) {
fontFamily = fontFamily.replaceAll('"', '"');
} else if (browserEngine == BrowserEngine.blink ||
@@ -22,6 +22,8 @@
}
return 'font-family: $fontFamily;';
}
+
+final String defaultFontFamily = fontFamilyToAttribute('Ahem');
const String defaultColor = 'color: rgb(255, 0, 0);';
const String defaultFontSize = 'font-size: 14px;';
final String paragraphStyle =
@@ -52,7 +54,7 @@
paragraph.layout(ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
- '<p style="$paragraphStyle">'
+ '<p style="font-size: 13px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor font-size: 13px; $defaultFontFamily">'
'Hello'
'</span>'
@@ -63,7 +65,7 @@
paragraph.layout(ParagraphConstraints(width: 39.0));
expect(
paragraph.toDomElement().outerHtml,
- '<p style="$paragraphStyle">'
+ '<p style="font-size: 13px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor font-size: 13px; $defaultFontFamily">'
'Hel<br>lo'
'</span>'
@@ -91,7 +93,11 @@
paragraph.layout(ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
- '<p style="$paragraphStyle"><span style="$defaultColor $defaultFontSize $defaultFontFamily">Hello</span></p>',
+ '<p style="$defaultFontSize $defaultFontFamily $paragraphStyle">'
+ '<span style="$defaultColor $defaultFontSize $defaultFontFamily">'
+ 'Hello'
+ '</span>'
+ '</p>',
);
final FlatTextSpan textSpan = paragraph.spans.single as FlatTextSpan;
@@ -116,7 +122,7 @@
paragraph.layout(ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
- '<p style="$paragraphStyle overflow-y: hidden; height: ${expectedHeight}px;">'
+ '<p style="$defaultFontSize $defaultFontFamily $paragraphStyle overflow-y: hidden; height: ${expectedHeight}px;">'
'<span style="$defaultColor $defaultFontSize $defaultFontFamily">'
'Hello'
'</span>'
@@ -142,7 +148,7 @@
paragraph.layout(ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
- '<p style="$paragraphStyle overflow: hidden; height: ${expectedHeight}px; text-overflow: ellipsis;">'
+ '<p style="$defaultFontSize $defaultFontFamily $paragraphStyle overflow: hidden; height: ${expectedHeight}px; text-overflow: ellipsis;">'
'<span style="$defaultColor $defaultFontSize $defaultFontFamily">'
'Hello'
'</span>'
@@ -170,7 +176,7 @@
paragraph.layout(ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
- '<p style="line-height: 1.5; $paragraphStyle">'
+ '<p style="line-height: 1.5; font-size: 9px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor line-height: 1.5; font-size: 9px; font-weight: bold; font-style: italic; $defaultFontFamily letter-spacing: 2px;">'
'Hello'
'</span>'
@@ -208,7 +214,7 @@
paragraph.layout(ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
- '<p style="$paragraphStyle">'
+ '<p style="font-size: 13px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor font-size: 13px; font-weight: bold; $defaultFontFamily">'
'Hello'
'</span>'
@@ -222,7 +228,7 @@
paragraph.layout(ParagraphConstraints(width: 75.0));
expect(
paragraph.toDomElement().outerHtml,
- '<p style="$paragraphStyle width: 75px;">'
+ '<p style="font-size: 13px; $defaultFontFamily $paragraphStyle width: 75px;">'
'<span style="$defaultColor font-size: 13px; font-weight: bold; $defaultFontFamily">'
'Hello'
'</span>'
@@ -273,7 +279,7 @@
paragraph.layout(ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
- '<p style="$paragraphStyle">'
+ '<p style="font-size: 13px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor line-height: 2; font-size: 13px; font-weight: bold; $defaultFontFamily">'
'Hello'
'</span>'
@@ -337,7 +343,7 @@
paragraph.layout(ParagraphConstraints(width: double.infinity));
expect(
paragraph.toDomElement().outerHtml,
- '<p style="$paragraphStyle">'
+ '<p style="font-size: 13px; $defaultFontFamily $paragraphStyle">'
'<span style="$defaultColor font-size: 13px; $defaultFontFamily">'
'First<br>Second '
'</span>'
@@ -351,7 +357,7 @@
paragraph.layout(ParagraphConstraints(width: 180.0));
expect(
paragraph.toDomElement().outerHtml,
- '<p style="$paragraphStyle width: 180px;">'
+ '<p style="font-size: 13px; $defaultFontFamily $paragraphStyle width: 180px;">'
'<span style="$defaultColor font-size: 13px; $defaultFontFamily">'
'First<br>Second <br>'
'</span>'
@@ -361,6 +367,44 @@
'</p>',
);
});
+
+ test('various font sizes', () {
+ // Paragraphs and spans force the Ahem font in test mode. We need to trick
+ // them into thinking they are not in test mode, so they use the provided
+ // font family.
+ debugEmulateFlutterTesterEnvironment = false;
+ final EngineParagraphStyle style = EngineParagraphStyle(fontSize: 12.0, fontFamily: 'first');
+ final CanvasParagraphBuilder builder = CanvasParagraphBuilder(style);
+
+ builder.addText('First ');
+ builder.pushStyle(TextStyle(fontSize: 18.0, fontFamily: 'second'));
+ builder.addText('Second ');
+ builder.pushStyle(TextStyle(fontSize: 10.0, fontFamily: 'third'));
+ builder.addText('Third');
+
+ final CanvasParagraph paragraph = builder.build();
+ expect(paragraph.toPlainText(), 'First Second Third');
+ expect(paragraph.spans, hasLength(3));
+
+ // The paragraph should take the font size and family from the span with the
+ // greatest font size.
+ paragraph.layout(ParagraphConstraints(width: double.infinity));
+ expect(
+ paragraph.toDomElement().outerHtml,
+ '<p style="font-size: 18px; ${fontFamilyToAttribute('second')} $paragraphStyle">'
+ '<span style="$defaultColor font-size: 12px; ${fontFamilyToAttribute('first')}">'
+ 'First '
+ '</span>'
+ '<span style="$defaultColor font-size: 18px; ${fontFamilyToAttribute('second')}">'
+ 'Second '
+ '</span>'
+ '<span style="$defaultColor font-size: 10px; ${fontFamilyToAttribute('third')}">'
+ 'Third'
+ '</span>'
+ '</p>',
+ );
+ debugEmulateFlutterTesterEnvironment = true;
+ });
}
TextStyle styleWithDefaults({