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> 明日
-<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
-
-
-
-
-
- |
-
-
- - One
- -
- Two
-
- - 2.1
- - 2.2
- -
- 2.3
-
- - 2.3.1
- - 2.3.2
-
-
-
-
-
- |
-
-
-
-
-<TABLE> with colspan / rowspan:
-
-
- | colspan=2 |
-
-
- | rowspan=2 |
- Foo |
-
-
- | Bar |
-
-
-
-<AUDIO>
-
-
- Source: developer.mozilla.org
-
-
-<IFRAME> of YouTube:
-
-
-<SVG> of Flutter logo
-
-
-<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)]]]',
+ ));
+ });
+ });
+}