diff --git a/lib/web_ui/lib/src/engine/embedder.dart b/lib/web_ui/lib/src/engine/embedder.dart index 6a27d910916cc..0fe5de1d9df75 100644 --- a/lib/web_ui/lib/src/engine/embedder.dart +++ b/lib/web_ui/lib/src/engine/embedder.dart @@ -525,20 +525,15 @@ void applyGlobalCssRulesToSheet( // TODO(web): use more efficient CSS selectors; descendant selectors are slow. // More info: https://csswizardry.com/2011/09/writing-efficient-css-selectors - // This undoes browser's default layout attributes for paragraphs. We - // compute paragraph layout ourselves. if (isFirefox) { // For firefox set line-height, otherwise textx at same font-size will // measure differently in ruler. + // + // - See: https://github.com/flutter/flutter/issues/44803 sheet.insertRule( - 'flt-ruler-host p, flt-scene p ' - '{ margin: 0; line-height: 100%;}', - sheet.cssRules.length); - } else { - sheet.insertRule( - 'flt-ruler-host p, flt-scene p ' - '{ margin: 0; }', - sheet.cssRules.length); + 'flt-paragraph, flt-span {line-height: 100%;}', + sheet.cssRules.length, + ); } // This undoes browser's default painting and layout attributes of range 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 9d86fd49dda1d..b291549ecb3c5 100644 --- a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart @@ -9,7 +9,6 @@ import 'package:ui/ui.dart' as ui; import '../embedder.dart'; import '../html/bitmap_canvas.dart'; import '../profiler.dart'; -import '../util.dart'; import 'layout_service.dart'; import 'paint_service.dart'; import 'paragraph.dart'; @@ -148,11 +147,10 @@ class CanvasParagraph implements ui.Paragraph { html.HtmlElement _createDomElement() { final html.HtmlElement rootElement = - html.document.createElement('p') as html.HtmlElement; + html.document.createElement('flt-paragraph') as html.HtmlElement; // 1. Set paragraph-level styles. - _applyNecessaryParagraphStyles(element: rootElement, style: paragraphStyle); - _applySpanStylesToParagraph(element: rootElement, spans: spans); + final html.CssStyleDeclaration cssStyle = rootElement.style; cssStyle ..position = 'absolute' @@ -175,7 +173,7 @@ class CanvasParagraph implements ui.Paragraph { final RangeBox box = boxes[j++]; if (box is SpanBox) { - lastSpanElement = html.document.createElement('span') as html.HtmlElement; + lastSpanElement = html.document.createElement('flt-span') as html.HtmlElement; applyTextStyleToElement( element: lastSpanElement, style: box.span.style, @@ -253,67 +251,6 @@ class CanvasParagraph implements ui.Paragraph { } } -/// Applies a paragraph [style] to an [element], translating the properties to -/// their corresponding CSS equivalents. -/// -/// As opposed to [_applyParagraphStyleToElement], this method only applies -/// styles that are necessary at the paragraph level. Other styles (e.g. font -/// size) are always applied at the span level so they aren't needed at the -/// paragraph level. -void _applyNecessaryParagraphStyles({ - required html.HtmlElement element, - required EngineParagraphStyle style, -}) { - final html.CssStyleDeclaration cssStyle = element.style; - - // TODO(mdebbar): Now that we absolutely position each span inside the - // paragraph, do we still need these style on

? - - if (style.textAlign != null) { - cssStyle.textAlign = textAlignToCssValue( - style.textAlign, style.textDirection ?? ui.TextDirection.ltr); - } - if (style.lineHeight != null) { - cssStyle.lineHeight = '${style.lineHeight}'; - } - if (style.textDirection != null) { - cssStyle.direction = textDirectionToCss(style.textDirection); - } -} - -/// 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); - } -} - void _positionSpanElement(html.Element element, EngineLineMetrics line, RangeBox box) { final ui.Rect boxRect = box.toTextBox(line, forPainting: true).toRect(); element.style diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index 590e126603e43..0c34403a48c11 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -759,51 +759,6 @@ void applyTextStyleToElement({ } } -html.Element createPlaceholderElement({ - required ParagraphPlaceholder placeholder, -}) { - final html.Element element = html.document.createElement('span'); - final html.CssStyleDeclaration style = element.style; - style - ..display = 'inline-block' - ..width = '${placeholder.width}px' - ..height = '${placeholder.height}px' - ..verticalAlign = _placeholderAlignmentToCssVerticalAlign(placeholder); - - return element; -} - -String _placeholderAlignmentToCssVerticalAlign( - ParagraphPlaceholder placeholder, -) { - // For more details about the vertical-align CSS property, see: - // - https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align - switch (placeholder.alignment) { - case ui.PlaceholderAlignment.top: - return 'top'; - - case ui.PlaceholderAlignment.middle: - return 'middle'; - - case ui.PlaceholderAlignment.bottom: - return 'bottom'; - - case ui.PlaceholderAlignment.aboveBaseline: - return 'baseline'; - - case ui.PlaceholderAlignment.belowBaseline: - return '-${placeholder.height}px'; - - case ui.PlaceholderAlignment.baseline: - // In CSS, the placeholder is already placed above the baseline. But - // Flutter's `baselineOffset` assumes the placeholder is placed below the - // baseline. That's why we need to subtract the placeholder's height from - // `baselineOffset`. - final double offset = placeholder.baselineOffset - placeholder.height; - return '${offset}px'; - } -} - String _shadowListToCss(List shadows) { if (shadows.isEmpty) { return ''; @@ -881,37 +836,6 @@ String? _decorationStyleToCssString(ui.TextDecorationStyle decorationStyle) { } } -/// Converts [textDirection] to its corresponding CSS value. -/// -/// This value is used for the "direction" CSS property, e.g.: -/// -/// ```css -/// direction: rtl; -/// ``` -String? textDirectionToCss(ui.TextDirection? textDirection) { - if (textDirection == null) { - return null; - } - return textDirectionIndexToCss(textDirection.index); -} - -String? textDirectionIndexToCss(int textDirectionIndex) { - switch (textDirectionIndex) { - case 0: - return 'rtl'; - case 1: - return null; // ltr is the default - } - - assert(() { - throw AssertionError( - 'Failed to convert text direction $textDirectionIndex to CSS', - ); - }()); - - return null; -} - /// Converts [align] to its corresponding CSS value. /// /// This value is used as the "text-align" CSS property, e.g.: diff --git a/lib/web_ui/lib/src/engine/text/ruler.dart b/lib/web_ui/lib/src/engine/text/ruler.dart index 1bb267b91c80b..fac90ef52b20a 100644 --- a/lib/web_ui/lib/src/engine/text/ruler.dart +++ b/lib/web_ui/lib/src/engine/text/ruler.dart @@ -95,7 +95,7 @@ class TextHeightStyle { class TextDimensions { TextDimensions(this._element); - final html.HtmlElement _element; + final html.Element _element; html.Rectangle? _cachedBoundingClientRect; void _invalidateBoundsCache() { @@ -165,7 +165,7 @@ class TextHeightRuler { // Elements used to measure the line-height metric. late final html.HtmlElement _probe = _createProbe(); late final html.HtmlElement _host = _createHost(); - final TextDimensions _dimensions = TextDimensions(html.ParagraphElement()); + final TextDimensions _dimensions = TextDimensions(html.document.createElement('flt-paragraph')); /// The alphabetic baseline for this ruler's [textHeightStyle]. late final double alphabeticBaseline = _probe.getBoundingClientRect().bottom.toDouble(); diff --git a/lib/web_ui/test/engine/host_node_test.dart b/lib/web_ui/test/engine/host_node_test.dart index a2364c86d01df..0d18398dd8a9b 100644 --- a/lib/web_ui/test/engine/host_node_test.dart +++ b/lib/web_ui/test/engine/host_node_test.dart @@ -66,7 +66,7 @@ void _runDomTests(HostNode hostNode) { hostNode.nodes.addAll([ html.document.createElement('div'), target, - html.document.createElement('span'), + html.document.createElement('flt-span'), html.document.createElement('div'), ]); }); diff --git a/lib/web_ui/test/engine/surface/scene_builder_test.dart b/lib/web_ui/test/engine/surface/scene_builder_test.dart index b6a1d1569c264..7e230cf8b1840 100644 --- a/lib/web_ui/test/engine/surface/scene_builder_test.dart +++ b/lib/web_ui/test/engine/surface/scene_builder_test.dart @@ -513,10 +513,10 @@ void testMain() { renderedLayers[char] = pushChild(builder, char, oldLayer: renderedLayers[char]); } final SurfaceScene scene = builder.build(); - final List pTags = scene.webOnlyRootElement!.querySelectorAll('p'); + final List pTags = scene.webOnlyRootElement!.querySelectorAll('flt-paragraph'); expect(pTags, hasLength(string.length)); expect( - scene.webOnlyRootElement!.querySelectorAll('p').map((html.Element p) => p.innerText).join(''), + scene.webOnlyRootElement!.querySelectorAll('flt-paragraph').map((html.Element p) => p.innerText).join(''), string, ); renderedLayers.removeWhere((String key, ui.EngineLayer value) => !string.contains(key)); diff --git a/lib/web_ui/test/html/bitmap_canvas_golden_test.dart b/lib/web_ui/test/html/bitmap_canvas_golden_test.dart index 5e339c8615274..a4b2f2af8eb4e 100644 --- a/lib/web_ui/test/html/bitmap_canvas_golden_test.dart +++ b/lib/web_ui/test/html/bitmap_canvas_golden_test.dart @@ -171,7 +171,7 @@ Future testMain() async { canvas.drawParagraph(paragraph, Offset(8.5, 8.5 + innerClip.top)); expect( - canvas.rootElement.querySelectorAll('p').map((html.Element e) => e.innerText).toList(), + canvas.rootElement.querySelectorAll('flt-paragraph').map((html.Element e) => e.innerText).toList(), ['Am I blurry?', 'Am I blurry?'], reason: 'Expected to render text using HTML', ); @@ -229,7 +229,7 @@ Future testMain() async { canvas.drawParagraph(paragraph, const Offset(180, 50)); expect( - canvas.rootElement.querySelectorAll('p').map((html.Element e) => e.text).toList(), + canvas.rootElement.querySelectorAll('flt-paragraph').map((html.Element e) => e.text).toList(), [text], reason: 'Expected to render text using HTML', ); diff --git a/lib/web_ui/test/html/compositing/compositing_golden_test.dart b/lib/web_ui/test/html/compositing/compositing_golden_test.dart index 4cfb4f4c2be13..d6339cd919b5d 100644 --- a/lib/web_ui/test/html/compositing/compositing_golden_test.dart +++ b/lib/web_ui/test/html/compositing/compositing_golden_test.dart @@ -872,7 +872,7 @@ void _testCullRectComputation() { final html.Element sceneElement = builder.build().webOnlyRootElement!; expect( sceneElement - .querySelectorAll('p') + .querySelectorAll('flt-paragraph') .map((html.Element e) => e.innerText) .toList(), ['Am I blurry?', 'Am I blurry?'], 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 bd7a526d4125e..82536da04349d 100644 --- a/lib/web_ui/test/text/canvas_paragraph_builder_test.dart +++ b/lib/web_ui/test/text/canvas_paragraph_builder_test.dart @@ -50,11 +50,11 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: double.infinity)); expectOuterHtml( paragraph, - '

' - '' + '' + '' 'Hello' - '' - '

', + '' + '', ignorePositions: !isBlink, ); @@ -62,14 +62,14 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: 39.0)); expectOuterHtml( paragraph, - '

' - '' + '' + '' 'Hel' - '' - '' + '' + '' 'lo' - '' - '

', + '' + '', ignorePositions: !isBlink, ); @@ -94,11 +94,11 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: double.infinity)); expect( paragraph.toDomElement().outerHtml, - '

' - '' + '' + '' 'Hello' - '' - '

', + '' + '', ); final FlatTextSpan textSpan = paragraph.spans.single as FlatTextSpan; @@ -118,11 +118,11 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: double.infinity)); expect( paragraph.toDomElement().outerHtml, - '

' - '' + '' + '' 'Hello' - '' - '

', + '' + '', ); }); @@ -139,11 +139,11 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: 100.0)); expect( paragraph.toDomElement().outerHtml, - '

' - '' + '' + '' 'Hell...' - '' - '

', + '' + '', ); }); @@ -167,11 +167,11 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: double.infinity)); expectOuterHtml( paragraph, - '

' - '' + '' + '' 'Hello' - '' - '

', + '' + '', ignorePositions: !isBlink, ); @@ -206,17 +206,17 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: double.infinity)); expectOuterHtml( paragraph, - '

' - '' + '' + '' 'Hello' - '' - '' + '' + '' ' ' - '' - '' + '' + '' 'world' - '' - '

', + '' + '', ignorePositions: !isBlink, ); @@ -224,18 +224,17 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: 75.0)); expectOuterHtml( paragraph, - '

' - '' + '' + '' 'Hello' - '' - // Trailing space. - '' + '' + '' ' ' - '' - '' + '' + '' 'world' - '' - '

', + '' + '', ignorePositions: !isBlink, ); @@ -280,20 +279,20 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: double.infinity)); expectOuterHtml( paragraph, - '

' - '' + '' + '' 'Hello' - '' - '' + '' + '' ' ' - '' - '' + '' + '' 'world' - '' - '' + '' + '' '!' - '' - '

', + '' + '', ignorePositions: !isBlink, ); @@ -348,20 +347,20 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: double.infinity)); expectOuterHtml( paragraph, - '

' - '' + '' + '' 'First' - '' - '' + '' + '' 'Second' - '' - '' + '' + '' ' ' - '' - '' + '' + '' 'ThirdLongLine' - '' - '

', + '' + '', ignorePositions: !isBlink, ); @@ -369,21 +368,21 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: 180.0)); expectOuterHtml( paragraph, - '

' - '' + '' + '' 'First' - '' - '' + '' + '' 'Second' - '' + '' // Trailing space. - '' + '' ' ' - '' - '' + '' + '' 'ThirdLongLine' - '' - '

', + '' + '', ignorePositions: !isBlink, ); }); @@ -411,23 +410,23 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: double.infinity)); expectOuterHtml( paragraph, - '

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

', + '' + '', // Since we are using unknown font families, we can't predict the text // measurements. ignorePositions: true, @@ -439,15 +438,8 @@ Future testMain() async { const String defaultFontFamily = 'Ahem'; const num defaultFontSize = 14; -String paragraphStyle({ - String fontFamily = defaultFontFamily, - num fontSize = defaultFontSize, - num? lineHeight, -}) { +String paragraphStyle() { return [ - if (lineHeight != null) 'line-height: $lineHeight;', - 'font-size: ${fontSize}px;', - 'font-family: ${fontFamilyToAttribute(fontFamily)};', 'position: absolute;', 'white-space: pre;', ].join(' '); diff --git a/lib/web_ui/test/text_test.dart b/lib/web_ui/test/text_test.dart index 5f0ff655a465d..2d0b6ade6734c 100644 --- a/lib/web_ui/test/text_test.dart +++ b/lib/web_ui/test/text_test.dart @@ -224,8 +224,8 @@ Future testMain() async { final CanvasParagraph paragraph = builder.build() as CanvasParagraph; paragraph.layout(const ParagraphConstraints(width: 800.0)); expect(paragraph.plainText, 'abcdef'); - final List spans = - paragraph.toDomElement().querySelectorAll('span'); + final List spans = + paragraph.toDomElement().querySelectorAll('flt-span'); expect(spans[0].style.fontFamily, 'Ahem, $fallback, sans-serif'); // The nested span here should not set it's family to default sans-serif. expect(spans[1].style.fontFamily, 'Ahem, $fallback, sans-serif'); @@ -245,7 +245,7 @@ Future testMain() async { ), 'Hello'); paragraph.layout(constrain(double.infinity)); - expect(paragraph.toDomElement().style.fontFamily, + expect(paragraph.toDomElement().children.single.style.fontFamily, 'SomeFont, $fallback, sans-serif'); debugEmulateFlutterTesterEnvironment = true; @@ -265,7 +265,7 @@ Future testMain() async { ), 'Hello'); paragraph.layout(constrain(double.infinity)); - expect(paragraph.toDomElement().style.fontFamily, 'serif'); + expect(paragraph.toDomElement().children.single.style.fontFamily, 'serif'); debugEmulateFlutterTesterEnvironment = true; }); @@ -280,7 +280,7 @@ Future testMain() async { ), 'Hello'); paragraph.layout(constrain(double.infinity)); - expect(paragraph.toDomElement().style.fontFamily, + expect(paragraph.toDomElement().children.single.style.fontFamily, '"MyFont 2000", $fallback, sans-serif'); debugEmulateFlutterTesterEnvironment = true;