From fe896de5ed8b79425bb33800a26fa4ac328057fe Mon Sep 17 00:00:00 2001 From: Matthew Whitaker Date: Mon, 15 May 2023 12:09:40 -0600 Subject: [PATCH] feat: support vertical-align in inline styles (#1266) --- lib/src/css_parser.dart | 26 ++++ lib/src/style.dart | 45 +++--- .../lib/flutter_html_table.dart | 1 - test/elements/a_test.dart | 2 +- test/golden_test.dart | 2 +- .../css_parsing/vertical_align_test.dart | 133 ++++++++++++++++++ test/{test_data.dart => test_utils.dart} | 17 +++ 7 files changed, 201 insertions(+), 25 deletions(-) create mode 100644 test/style/css_parsing/vertical_align_test.dart rename test/{test_data.dart => test_utils.dart} (93%) diff --git a/lib/src/css_parser.dart b/lib/src/css_parser.dart index 6ffde91df6..4859b3907b 100644 --- a/lib/src/css_parser.dart +++ b/lib/src/css_parser.dart @@ -538,6 +538,10 @@ Style declarationsToStyle(Map> declarations) { style.textTransform = TextTransform.none; } break; + case 'vertical-align': + style.verticalAlign = + ExpressionMapping.expressionToVerticalAlign(value.first); + break; case 'width': style.width = ExpressionMapping.expressionToWidth(value.first) ?? style.width; @@ -1212,6 +1216,28 @@ class ExpressionMapping { return finalShadows; } + static VerticalAlign expressionToVerticalAlign(css.Expression value) { + if (value is css.LiteralTerm) { + switch (value.text) { + case "sub": + return VerticalAlign.sub; + case "super": + return VerticalAlign.sup; + case "bottom": + return VerticalAlign.bottom; + case "top": + return VerticalAlign.top; + case "middle": + return VerticalAlign.middle; + case "baseline": + default: + return VerticalAlign.baseline; + } + } + + return VerticalAlign.baseline; + } + static Color stringToColor(String rawText) { var text = rawText.replaceFirst('#', ''); if (text.length == 3) { diff --git a/lib/src/style.dart b/lib/src/style.dart index f84f7cf99d..367c969053 100644 --- a/lib/src/style.dart +++ b/lib/src/style.dart @@ -177,8 +177,8 @@ class Style { /// CSS attribute "`vertical-align`" /// /// Inherited: no, - /// Default: VerticalAlign.BASELINE, - VerticalAlign? verticalAlign; + /// Default: VerticalAlign.baseline, + VerticalAlign verticalAlign; /// CSS attribute "`white-space`" /// @@ -259,7 +259,7 @@ class Style { this.textDecorationStyle, this.textDecorationThickness, this.textShadow, - this.verticalAlign, + this.verticalAlign = VerticalAlign.baseline, this.whiteSpace, this.width, this.wordSpacing, @@ -503,25 +503,26 @@ class Style { ); } - Style.fromTextStyle(TextStyle textStyle) { - backgroundColor = textStyle.backgroundColor; - color = textStyle.color; - textDecoration = textStyle.decoration; - textDecorationColor = textStyle.decorationColor; - textDecorationStyle = textStyle.decorationStyle; - textDecorationThickness = textStyle.decorationThickness; - fontFamily = textStyle.fontFamily; - fontFamilyFallback = textStyle.fontFamilyFallback; - fontFeatureSettings = textStyle.fontFeatures; - fontSize = - textStyle.fontSize != null ? FontSize(textStyle.fontSize!) : null; - fontStyle = textStyle.fontStyle; - fontWeight = textStyle.fontWeight; - letterSpacing = textStyle.letterSpacing; - textShadow = textStyle.shadows; - wordSpacing = textStyle.wordSpacing; - lineHeight = LineHeight(textStyle.height ?? 1.2); - textTransform = TextTransform.none; + factory Style.fromTextStyle(TextStyle textStyle) { + return Style( + backgroundColor: textStyle.backgroundColor, + color: textStyle.color, + textDecoration: textStyle.decoration, + textDecorationColor: textStyle.decorationColor, + textDecorationStyle: textStyle.decorationStyle, + textDecorationThickness: textStyle.decorationThickness, + fontFamily: textStyle.fontFamily, + fontFamilyFallback: textStyle.fontFamilyFallback, + fontFeatureSettings: textStyle.fontFeatures, + fontSize: + textStyle.fontSize != null ? FontSize(textStyle.fontSize!) : null, + fontStyle: textStyle.fontStyle, + fontWeight: textStyle.fontWeight, + letterSpacing: textStyle.letterSpacing, + textShadow: textStyle.shadows, + wordSpacing: textStyle.wordSpacing, + lineHeight: LineHeight(textStyle.height ?? 1.2), + ); } /// Sets any dimensions set to rem or em to the computed size diff --git a/packages/flutter_html_table/lib/flutter_html_table.dart b/packages/flutter_html_table/lib/flutter_html_table.dart index a9bee64586..37571244d8 100644 --- a/packages/flutter_html_table/lib/flutter_html_table.dart +++ b/packages/flutter_html_table/lib/flutter_html_table.dart @@ -290,7 +290,6 @@ Alignment _getCellAlignment(TableCellElement cell, TextDirection alignment) { Alignment verticalAlignment; switch (cell.style.verticalAlign) { - case null: case VerticalAlign.baseline: case VerticalAlign.sub: case VerticalAlign.sup: diff --git a/test/elements/a_test.dart b/test/elements/a_test.dart index d5fd2b8270..4b67971773 100644 --- a/test/elements/a_test.dart +++ b/test/elements/a_test.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../test_data.dart'; +import '../test_utils.dart'; void main() { testWidgets(' test', (WidgetTester tester) async { diff --git a/test/golden_test.dart b/test/golden_test.dart index 1f62cfb414..c34f05bd6e 100644 --- a/test/golden_test.dart +++ b/test/golden_test.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'test_data.dart'; +import 'test_utils.dart'; class TestApp extends StatelessWidget { final Widget body; diff --git a/test/style/css_parsing/vertical_align_test.dart b/test/style/css_parsing/vertical_align_test.dart new file mode 100644 index 0000000000..54f2986c1c --- /dev/null +++ b/test/style/css_parsing/vertical_align_test.dart @@ -0,0 +1,133 @@ +import 'package:flutter_html/flutter_html.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../test_utils.dart'; + +void main() { + testWidgets( + 'Tag with vertical align set inline should receive that style', + (tester) async { + await tester.pumpWidget( + TestApp( + child: Html( + data: """ + Text + """, + ), + ), + ); + expect(find.text("Text", findRichText: true), findsOneWidget); + expect( + findCssBox(find.text("Text", findRichText: true))! + .style + .verticalAlign, + equals(VerticalAlign.sup)); + }, + ); + + testWidgets( + 'Tag with vertical align set in style tag should receive that style', + (tester) async { + await tester.pumpWidget( + TestApp( + child: Html( + data: """ + + Text + """, + ), + ), + ); + expect(find.text("Text", findRichText: true), findsOneWidget); + expect( + findCssBox(find.text("Text", findRichText: true))! + .style + .verticalAlign, + equals(VerticalAlign.sub)); + }, + ); + + testWidgets( + 'Tag with no vertical align set should have default', + (tester) async { + await tester.pumpWidget( + TestApp( + child: Html( + data: """ + Text + """, + ), + ), + ); + expect(find.text("Text", findRichText: true), findsOneWidget); + expect( + findCssBox(find.text("Text", findRichText: true))! + .style + .verticalAlign, + equals(VerticalAlign.baseline)); + }, + ); + + testWidgets( + 'Tag with vertical align bottom set should have that value', + (tester) async { + await tester.pumpWidget( + TestApp( + child: Html( + data: """ +
Text
+ """, + ), + ), + ); + expect(find.text("Text", findRichText: true), findsOneWidget); + expect( + findCssBox(find.text("Text", findRichText: true))! + .style + .verticalAlign, + equals(VerticalAlign.bottom)); + }, + ); + + testWidgets( + 'Tag with vertical align middle set should have that value', + (tester) async { + await tester.pumpWidget( + TestApp( + child: Html( + data: """ +
Text
+ """, + ), + ), + ); + expect(find.text("Text", findRichText: true), findsOneWidget); + expect( + findCssBox(find.text("Text", findRichText: true))! + .style + .verticalAlign, + equals(VerticalAlign.middle)); + }, + ); + + testWidgets( + 'Tag with vertical align top set should have that value', + (tester) async { + await tester.pumpWidget( + TestApp( + child: Html( + data: """ +
Text
+ """, + ), + ), + ); + expect(find.text("Text", findRichText: true), findsOneWidget); + expect( + findCssBox(find.text("Text", findRichText: true))! + .style + .verticalAlign, + equals(VerticalAlign.top)); + }, + ); +} diff --git a/test/test_data.dart b/test/test_utils.dart similarity index 93% rename from test/test_data.dart rename to test/test_utils.dart index 0114cf2ed7..9a1a00625d 100644 --- a/test/test_data.dart +++ b/test/test_utils.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_html/src/css_box_widget.dart'; +import 'package:flutter_test/flutter_test.dart'; class TestApp extends StatelessWidget { final Widget child; @@ -124,3 +126,18 @@ const testData = { 'u': 'Hello, World!', 'var': 'Hello, World!', }; + +CssBoxWidget? findCssBox(Finder finder) { + final boxFinder = find.ancestor( + of: finder, + matching: find.byType(CssBoxWidget), + ); + + final found = boxFinder.evaluate(); + + if (found.isEmpty) { + return null; + } else { + return found.first.widget as CssBoxWidget; + } +}