diff --git a/demo_app/lib/screens/hello_world.dart b/demo_app/lib/screens/hello_world.dart index 15c0ca47d..b105efdc5 100644 --- a/demo_app/lib/screens/hello_world.dart +++ b/demo_app/lib/screens/hello_world.dart @@ -3,145 +3,18 @@ import 'package:demo_app/widgets/selection_area.dart'; import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; +//

Heading 1

+//

Heading 2

+// Heading 3 +//
const kHtml = ''' -

Heading 1

-

Heading 2

-

Heading 3

-

Heading 4

-
Heading 5
-
Heading 6
- -

- A paragraph with <strong>, <em>phasized - and colored text. - With an inline logo: - . -

- -

<RUBY> 明日 (Ashita)

-

<SUB> C8H10N4O2

-

<SUP> a2 + b2 = c2

- -

<IMG> of a cat:

-
- -
Source: https://gph.is/QFgPA0
-
- -

Lists:

- - - - - - - - - -
<UL> unordered list<OL> ordered list
-
    -
  • One
  • -
  • - Two -
      -
    • 2.1
    • -
    • 2.2
    • -
    • - 2.3 -
        -
      • 2.3.1
      • -
      • 2.3.2
      • -
      -
    • -
    -
  • -
-
-
    -
  1. One
  2. -
  3. - Two -
      -
    1. 2.1
    2. -
    3. 2.2
    4. -
    5. - 2.3 -
        -
      1. 2.3.1
      2. -
      3. 2.3.2
      4. - - - - - -
-
- -

<TABLE> with colspan / rowspan:

- - - - - - - - - - - -
colspan=2
rowspan=2Foo
Bar
- -

<AUDIO>

-
- -
Source: developer.mozilla.org
-
- -

<IFRAME> of YouTube:

- - -

<SVG> of Flutter logo

- - - - - - - - - - - - - - - - - - - - - SVG support is not enabled. - - -

<VIDEO>

-
- -
Source: developer.mozilla.org
-
- -

Anchor: Scroll to top.

-
-
+Heading 4 '''; +// const kHtml = ''' +//

Heading 1

+// '''; + class HelloWorldScreen extends StatelessWidget { const HelloWorldScreen({super.key}); diff --git a/packages/core/lib/src/core_widget_factory.dart b/packages/core/lib/src/core_widget_factory.dart index e099e38c0..d860df769 100644 --- a/packages/core/lib/src/core_widget_factory.dart +++ b/packages/core/lib/src/core_widget_factory.dart @@ -1126,6 +1126,17 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory { case kCssTextShadow: textShadowApply(tree, style); break; + + case kCssTextTransform: + print('zzll yep kCssTextTransform'); + final textTransform = style.term != null + ? text_ops.tryParseTextTransform(style.term) + : null; + if (textTransform != null) { + print('zzll yep kCssTextTransform != null'); + tree.inherit(text_ops.textTransform, textTransform); + } + break; } if (key.startsWith(kCssBackground)) { diff --git a/packages/core/lib/src/data/css.dart b/packages/core/lib/src/data/css.dart index 1221e9ce5..3a2fc0012 100644 --- a/packages/core/lib/src/data/css.dart +++ b/packages/core/lib/src/data/css.dart @@ -439,6 +439,30 @@ enum CssWhitespace { pre, } +/// https://developer.mozilla.org/en-US/docs/Web/CSS/text-transform +/// TODO: add support for +/// Keyword values +/// text-transform: full-width; +/// text-transform: full-size-kana; +/// Global values +/// text-transform: inherit; +/// text-transform: initial; +/// text-transform: revert; +/// text-transform: revert-layer; +/// text-transform: unset; +enum CssTextTransform { + none, + + /// toUpperCase every first letter of a word + capitalize, + + /// toLoweCase all characters + uppercase, + + /// toUpperCase all characters + lowercase, +} + extension on InheritedProperties { bool get isRtl => get() == TextDirection.rtl; } diff --git a/packages/core/lib/src/internal/core_ops.dart b/packages/core/lib/src/internal/core_ops.dart index 5e5baf12d..795aafff9 100644 --- a/packages/core/lib/src/internal/core_ops.dart +++ b/packages/core/lib/src/internal/core_ops.dart @@ -95,6 +95,12 @@ const kCssWhitespaceNormal = 'normal'; const kCssWhitespaceNowrap = 'nowrap'; const kCssWhitespacePre = 'pre'; +const kCssTextTransform = 'text-transform'; +const kCssTextTransformNone = 'none'; +const kCssTextTransformCapitalize = 'capitalize'; +const kCssTextTransformUppercase = 'uppercase'; +const kCssTextTransformLowercase = 'lowercase'; + extension on InheritedProperties { TextDirection get directionOrLtr => get() ?? TextDirection.ltr; } diff --git a/packages/core/lib/src/internal/flattener.dart b/packages/core/lib/src/internal/flattener.dart index 126105df6..7f0a351d2 100644 --- a/packages/core/lib/src/internal/flattener.dart +++ b/packages/core/lib/src/internal/flattener.dart @@ -5,6 +5,7 @@ import 'package:logging/logging.dart'; import '../core_data.dart'; import '../core_helpers.dart'; import '../core_widget_factory.dart'; +import '../utils/string_utils.dart'; import 'core_ops.dart'; import 'margin_vertical.dart'; @@ -72,6 +73,7 @@ class Flattener implements Flattened { @override void widget(Widget value) { + print('zzll widget () '); _completeLoop(); final debugLabel = '${_bit.parent.element.localName}--Flattener.widget'; @@ -196,6 +198,8 @@ class Flattener implements Flattened { final placeholder = WidgetPlaceholder( builder: (context, _) { final resolved = scopedInheritanceResolvers.resolve(context); + print('zzll ya ${resolved.get()}'); + print('zzll ${resolved.values}'); final children = []; var isLast_ = true; @@ -207,11 +211,13 @@ class Flattener implements Flattened { } } - final text = scopedStrings.toText( - resolved.whitespaceOrNormal, - isFirst: true, - isLast: isLast_, - ); + final text = scopedStrings + .toText( + resolved.whitespaceOrNormal, + isFirst: true, + isLast: isLast_, + ) + .applyTextTransform(resolved.get()); InlineSpan? span; if (text.isEmpty && children.isEmpty) { final nonWhitespaceStrings = scopedStrings @@ -275,6 +281,21 @@ class Flattener implements Flattened { } } +extension on String { + String applyTextTransform(CssTextTransform? textTransform) { + switch (textTransform) { + case CssTextTransform.uppercase: + return toUpperCase(); + case CssTextTransform.lowercase: + return toLowerCase(); + case CssTextTransform.capitalize: + return toUpperCaseAllWords(); + default: + return this; + } + } +} + extension on BuildBit { InheritanceResolvers? get effectiveInheritanceResolvers { // the below code will find the best resolvers for this whitespace bit diff --git a/packages/core/lib/src/internal/text_ops.dart b/packages/core/lib/src/internal/text_ops.dart index 5b0880012..a3792dc76 100644 --- a/packages/core/lib/src/internal/text_ops.dart +++ b/packages/core/lib/src/internal/text_ops.dart @@ -253,3 +253,22 @@ CssWhitespace? whitespaceTryParse(String value) { return null; } + +InheritedProperties textTransform( + InheritedProperties resolving, + CssTextTransform textTransform, +) => + resolving.copyWith(value: textTransform); + +CssTextTransform tryParseTextTransform(String? value) { + switch (value) { + case kCssTextTransformCapitalize: + return CssTextTransform.capitalize; + case kCssTextTransformUppercase: + return CssTextTransform.uppercase; + case kCssTextTransformLowercase: + return CssTextTransform.lowercase; + default: + return CssTextTransform.none; + } +} diff --git a/packages/core/lib/src/utils/string_utils.dart b/packages/core/lib/src/utils/string_utils.dart new file mode 100644 index 000000000..d60d5c86d --- /dev/null +++ b/packages/core/lib/src/utils/string_utils.dart @@ -0,0 +1,9 @@ +extension StringExt on String { + String toUpperCaseFirstLetter() { + return '${substring(0, 1).toUpperCase()}${substring(1)}'; + } + + String toUpperCaseAllWords() { + return split(' ').map((e) => e.toUpperCaseFirstLetter()).join(' '); + } +} diff --git a/packages/core/test/src/internal/text_ops_test.dart b/packages/core/test/src/internal/text_ops_test.dart index 6cc9832eb..30d9a453b 100644 --- a/packages/core/test/src/internal/text_ops_test.dart +++ b/packages/core/test/src/internal/text_ops_test.dart @@ -1,4 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_widget_from_html_core/src/core_data.dart'; +import 'package:flutter_widget_from_html_core/src/internal/text_ops.dart'; import '../../_.dart'; @@ -99,4 +101,36 @@ Future main() async { }); }); }); + + group('text-transformation', () { + test('test tryParseTextTransform capitalize', () { + const value = 'capitalize'; + final actual = tryParseTextTransform(value); + expect(actual, CssTextTransform.capitalize); + }); + + test('test tryParseTextTransform uppercase', () { + const value = 'uppercase'; + final actual = tryParseTextTransform(value); + expect(actual, CssTextTransform.uppercase); + }); + + test('test tryParseTextTransform lowercase', () { + const value = 'lowercase'; + final actual = tryParseTextTransform(value); + expect(actual, CssTextTransform.lowercase); + }); + + test('test tryParseTextTransform none', () { + const value = 'none'; + final actual = tryParseTextTransform(value); + expect(actual, CssTextTransform.none); + }); + + test('test tryParseTextTransform default', () { + const value = 'random string'; + final actual = tryParseTextTransform(value); + expect(actual, CssTextTransform.none); + }); + }); } diff --git a/packages/core/test/src/utils/string_utils_test.dart b/packages/core/test/src/utils/string_utils_test.dart new file mode 100644 index 000000000..e499f91e2 --- /dev/null +++ b/packages/core/test/src/utils/string_utils_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_widget_from_html_core/src/utils/string_utils.dart'; + +void main() { + group('test toUpperCaseFirstLetter', () { + test('test toUpperCaseFirstLetter', () { + const text = 'characteristics'; + final actual = text.toUpperCaseFirstLetter(); + expect(actual, 'Characteristics'); + }); + + test('test toUpperCaseFirstLetter keep other chars case', () { + const text = 'characTERIstics'; + final actual = text.toUpperCaseFirstLetter(); + expect(actual, 'CharacTERIstics'); + }); + }); + + group('test toUpperCaseAllWords', () { + test('test toUpperCaseAllWords basic case', () { + const text = 'this is a very simple test'; + final actual = text.toUpperCaseAllWords(); + expect(actual, 'This Is A Very Simple Test'); + }); + + test('test toUpperCaseAllWords multiple case', () { + const text = 'ThIs iS a VERy s1mple tESt'; + final actual = text.toUpperCaseAllWords(); + expect(actual, 'ThIs IS A VERy S1mple TESt'); + }); + + test('test toUpperCaseAllWords special characters', () { + const text = 'this is_a Vey\$XVM ~tEKOCM'; + final actual = text.toUpperCaseAllWords(); + expect(actual, 'This Is_a Vey\$XVM ~tEKOCM'); + }); + }); +} diff --git a/packages/core/test/style_text_transformation_test.dart b/packages/core/test/style_text_transformation_test.dart new file mode 100644 index 000000000..fb2dde793 --- /dev/null +++ b/packages/core/test/style_text_transformation_test.dart @@ -0,0 +1,87 @@ +import 'package:flutter_test/flutter_test.dart'; + +import '_.dart'; + +void main() { + group('text-transform p tag', () { + testWidgets('text-transform: capitalize', (WidgetTester tester) async { + const html = '

rain man

'; + final explained = await explain(tester, html); + expect(explained, equals('[CssBlock:child=[RichText:(:Rain Man)]]')); + }); + + testWidgets('text-transform: uppercase', (WidgetTester tester) async { + const html = '

oppEnheimer

'; + final explained = await explain(tester, html); + expect(explained, equals('[CssBlock:child=[RichText:(:OPPENHEIMER)]]')); + }); + + testWidgets('text-transform: lowercase', (WidgetTester tester) async { + const html = + '

ThE Boy AnD THE heron

'; + final explained = await explain(tester, html); + expect(explained, + equals('[CssBlock:child=[RichText:(:the boy and the heron)]]')); + }); + + testWidgets('text-transform: none', (WidgetTester tester) async { + const html = '

The BeAr

'; + final explained = await explain(tester, html); + expect(explained, equals('[CssBlock:child=[RichText:(:The BeAr)]]')); + }); + + testWidgets('text-transform: invalid syntax', (WidgetTester tester) async { + const html = '

AlicE In wonDERLA nd

'; + final explained = await explain(tester, html); + expect(explained, + equals('[CssBlock:child=[RichText:(:AlicE In wonDERLA nd)]]')); + }); + }); + + group('text-transform other tags', () { + testWidgets('text-transform: uppercase, tag', + (WidgetTester tester) async { + const html = + 'raIn MAn'; + final explained = await explain(tester, html); + expect(explained, equals('[RichText:(#FF123456+u+onTap:RAIN MAN)]')); + }); + + testWidgets('text-transform: uppercase, tag', + (WidgetTester tester) async { + const html = 'raIn MAn'; + final explained = await explain(tester, html); + expect(explained, equals('[RichText:(+b:RAIN MAN)]')); + }); + + testWidgets('text-transform: uppercase, tag', + (WidgetTester tester) async { + const html = 'raIn MAn'; + final explained = await explain(tester, html); + print(explained); + expect(explained, equals('[RichText:(+b:RAIN MAN)]')); + }); + + testWidgets('text-transform: uppercase,

tag', + (WidgetTester tester) async { + const html = '

raIn MAn

'; + final explained = await explain(tester, html); + expect( + explained, + equals('[CssBlock:child=[RichText:(@20.0+b:RAIN MAN)]]'), + ); + }); + + testWidgets('text-transform: uppercase,
tag', + (WidgetTester tester) async { + const html = + '
raIn MAn
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[HorizontalMargin:left=40,right=40,child=[CssBlock:child=[RichText:(:RAIN MAN)]]]', + )); + }); + }); +}