diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index d081079ecf542e31df491151cb9aa1e519acbafe..f04236f2c4abe4a5e96ee90649f4fa5865d81326 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 ce9e1319606743703fa13ee2e6f5ef4cb7532d47..2f626de2ce7c6eaf6360d0d6b20065859369437e 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 @@ class CanvasParagraph implements EngineParagraph { // 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 @@ void _applyNecessaryParagraphStyles({ } } +/// 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 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 73a97debb96795a79e861d277312878980fca7d5..d86a7fd555688a841791055c1b3d0040b4aafdae 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 @@ void testMain() async { 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 3cd1ff5627b633493ae5c99a12b0909afeba150b..8c48e366fc34828c14e8d4d089f3441dec9a2941 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 @@ import 'package:ui/ui.dart'; 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 @@ String get defaultFontFamily { } 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 @@ void testMain() async { paragraph.layout(ParagraphConstraints(width: double.infinity)); expect( paragraph.toDomElement().outerHtml, - '

' + '

' '' 'Hello' '' @@ -63,7 +65,7 @@ void testMain() async { paragraph.layout(ParagraphConstraints(width: 39.0)); expect( paragraph.toDomElement().outerHtml, - '

' + '

' '' 'Hel
lo' '
' @@ -91,7 +93,11 @@ void testMain() async { paragraph.layout(ParagraphConstraints(width: double.infinity)); expect( paragraph.toDomElement().outerHtml, - '

Hello

', + '

' + '' + 'Hello' + '' + '

', ); final FlatTextSpan textSpan = paragraph.spans.single as FlatTextSpan; @@ -116,7 +122,7 @@ void testMain() async { paragraph.layout(ParagraphConstraints(width: double.infinity)); expect( paragraph.toDomElement().outerHtml, - '

' + '

' '' 'Hello' '' @@ -142,7 +148,7 @@ void testMain() async { paragraph.layout(ParagraphConstraints(width: double.infinity)); expect( paragraph.toDomElement().outerHtml, - '

' + '

' '' 'Hello' '' @@ -170,7 +176,7 @@ void testMain() async { paragraph.layout(ParagraphConstraints(width: double.infinity)); expect( paragraph.toDomElement().outerHtml, - '

' + '

' '' 'Hello' '' @@ -208,7 +214,7 @@ void testMain() async { paragraph.layout(ParagraphConstraints(width: double.infinity)); expect( paragraph.toDomElement().outerHtml, - '

' + '

' '' 'Hello' '' @@ -222,7 +228,7 @@ void testMain() async { paragraph.layout(ParagraphConstraints(width: 75.0)); expect( paragraph.toDomElement().outerHtml, - '

' + '

' '' 'Hello' '' @@ -273,7 +279,7 @@ void testMain() async { paragraph.layout(ParagraphConstraints(width: double.infinity)); expect( paragraph.toDomElement().outerHtml, - '

' + '

' '' 'Hello' '' @@ -337,7 +343,7 @@ void testMain() async { paragraph.layout(ParagraphConstraints(width: double.infinity)); expect( paragraph.toDomElement().outerHtml, - '

' + '

' '' 'First
Second ' '
' @@ -351,7 +357,7 @@ void testMain() async { paragraph.layout(ParagraphConstraints(width: 180.0)); expect( paragraph.toDomElement().outerHtml, - '

' + '

' '' 'First
Second
' '
' @@ -361,6 +367,44 @@ void testMain() async { '

', ); }); + + 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, + '

' + '' + 'First ' + '' + '' + 'Second ' + '' + '' + 'Third' + '' + '

', + ); + debugEmulateFlutterTesterEnvironment = true; + }); } TextStyle styleWithDefaults({