Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add basic support for text-transform #1162

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 9 additions & 136 deletions demo_app/lib/screens/hello_world.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

// <p style="text-transform: uppercase;">Heading 1</p>
// <h2 style="text-transform: uppercase;">Heading 2</h2>
// <b style="text-transform: uppercase;">Heading 3</b>
// <br>
const kHtml = '''
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>

<p>
<a name="top"></a>A paragraph with <strong>&lt;strong&gt;</strong>, <em>&lt;em&gt;phasized</em>
and <span style="color: red">colored</span> text.
With an inline logo:
<img src="https://raw.githubusercontent.com/daohoangson/flutter_widget_from_html/0000998/demo_app/logos/icon.png" style="width: 1em" />.
</p>

<p>&lt;RUBY&gt; <ruby style="font-size: 200%">明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp></ruby></td></p>
<p>&lt;SUB&gt; C<sub>8</sub>H<sub>10</sub>N<sub>4</sub>O<sub>2</sub></p>
<p>&lt;SUP&gt; <var>a<sup>2</sup></var> + <var>b<sup>2</sup></var> = <var>c<sup>2</sup></var></p>

<h4>&lt;IMG&gt; of a cat:</h4>
<figure>
<img src="https://media.giphy.com/media/6VoDJzfRjJNbG/giphy-downsized.gif" width="250" height="171" />
<figcaption>Source: <a href="https://gph.is/QFgPA0">https://gph.is/QFgPA0</a></figcaption>
</figure>

<h4>Lists:</h4>
<table border="1" cellpadding="4">
<tr>
<th>&lt;UL&gt; unordered list</th>
<th>&lt;OL&gt; ordered list</th>
</tr>
<tr>
<td>
<ul>
<li>One</li>
<li>
Two
<ul>
<li>2.1</li>
<li>2.2</li>
<li>
2.3
<ul>
<li>2.3.1</li>
<li>2.3.2</li>
</ul>
</li>
</ul>
</li>
</ul>
</td>
<td>
<ol>
<li>One</li>
<li>
Two
<ol type="a">
<li>2.1</li>
<li>2.2</li>
<li>
2.3
<ol type="i">
<li>2.3.1</li>
<li>2.3.2</li>
</ul>
</li>
</ul>
</li>
</ul>
</td>
</tr>
</table>
<br />

<h4>&lt;TABLE&gt; with colspan / rowspan:</h4>
<table border="1" cellpadding="4">
<tr>
<td colspan="2">colspan=2</td>
</tr>
<tr>
<td rowspan="2">rowspan=2</td>
<td>Foo</td>
</tr>
<tr>
<td>Bar</td>
</tr>
</table>

<h4>&lt;AUDIO&gt;</h4>
<figure>
<audio controls src="https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3">
<code>AUDIO</code> support is not enabled.
</audio>
<figcaption>Source: <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio">developer.mozilla.org</a></figcaption>
</figure>

<h4>&lt;IFRAME&gt; of YouTube:</h4>
<iframe src="https://www.youtube.com/embed/jNQXAC9IVRw" width="560" height="315">
IFRAME support is not enabled.
</iframe>

<h4>&lt;SVG&gt; of Flutter logo</h4>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 166 202">
<defs>
<linearGradient id="triangleGradient">
<stop offset="20%" stop-color="#000000" stop-opacity=".55" />
<stop offset="85%" stop-color="#616161" stop-opacity=".01" />
</linearGradient>
<linearGradient id="rectangleGradient" x1="0%" x2="0%" y1="0%" y2="100%">
<stop offset="20%" stop-color="#000000" stop-opacity=".15" />
<stop offset="85%" stop-color="#616161" stop-opacity=".01" />
</linearGradient>
</defs>
<path fill="#42A5F5" fill-opacity=".8" d="M37.7 128.9 9.8 101 100.4 10.4 156.2 10.4"/>
<path fill="#42A5F5" fill-opacity=".8" d="M156.2 94 100.4 94 79.5 114.9 107.4 142.8"/>
<path fill="#0D47A1" d="M79.5 170.7 100.4 191.6 156.2 191.6 156.2 191.6 107.4 142.8"/>
<g transform="matrix(0.7071, -0.7071, 0.7071, 0.7071, -77.667, 98.057)">
<rect width="39.4" height="39.4" x="59.8" y="123.1" fill="#42A5F5" />
<rect width="39.4" height="5.5" x="59.8" y="162.5" fill="url(#rectangleGradient)" />
</g>
<path d="M79.5 170.7 120.9 156.4 107.4 142.8" fill="url(#triangleGradient)" />

SVG support is not enabled.
</svg>

<h4>&lt;VIDEO&gt;</h4>
<figure>
<video controls width="250">
<source src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" type="video/mp4">
<code>VIDEO</code> support is not enabled.
</video>
<figcaption>Source: <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video">developer.mozilla.org</a></figcaption>
</figure>

<p>Anchor: <a href="#top">Scroll to top</a>.</p>
<br />
<br />
<abbr style="text-transform: uppercase;">Heading 4</abbr>
''';

// const kHtml = '''
// <p style="text-transform: uppercase;">Heading 1</p>
// ''';

class HelloWorldScreen extends StatelessWidget {
const HelloWorldScreen({super.key});

Expand Down
11 changes: 11 additions & 0 deletions packages/core/lib/src/core_widget_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
24 changes: 24 additions & 0 deletions packages/core/lib/src/data/css.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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>() == TextDirection.rtl;
}
6 changes: 6 additions & 0 deletions packages/core/lib/src/internal/core_ops.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
31 changes: 26 additions & 5 deletions packages/core/lib/src/internal/flattener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -196,6 +198,8 @@ class Flattener implements Flattened {
final placeholder = WidgetPlaceholder(
builder: (context, _) {
final resolved = scopedInheritanceResolvers.resolve(context);
print('zzll ya ${resolved.get<CssTextTransform>()}');
print('zzll ${resolved.values}');
final children = <InlineSpan>[];

var isLast_ = true;
Expand All @@ -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<CssTextTransform>());
InlineSpan? span;
if (text.isEmpty && children.isEmpty) {
final nonWhitespaceStrings = scopedStrings
Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions packages/core/lib/src/internal/text_ops.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
9 changes: 9 additions & 0 deletions packages/core/lib/src/utils/string_utils.dart
Original file line number Diff line number Diff line change
@@ -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(' ');
}
}
34 changes: 34 additions & 0 deletions packages/core/test/src/internal/text_ops_test.dart
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -99,4 +101,36 @@ Future<void> 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);
});
});
}
38 changes: 38 additions & 0 deletions packages/core/test/src/utils/string_utils_test.dart
Original file line number Diff line number Diff line change
@@ -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');
});
});
}
Loading
Loading