diff --git a/example/lib/main.dart b/example/lib/main.dart
index 5c3b5a986d..400ed012aa 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -41,6 +41,13 @@ const htmlData = """
dog.
The quick brown fox jumped over the lazy dog.
+
+
+ 漢かん
+ 字じ
+
+ is Japanese Kanji
+
One Two Three
Data Data Data
diff --git a/lib/html_parser.dart b/lib/html_parser.dart
index 8cad007316..a08272b541 100644
--- a/lib/html_parser.dart
+++ b/lib/html_parser.dart
@@ -243,7 +243,7 @@ class HtmlParser extends StatelessWidget {
return TextSpan(text: tree.text);
} else {
return WidgetSpan(
- alignment: PlaceholderAlignment.aboveBaseline,
+ alignment: tree.alignment,
baseline: TextBaseline.alphabetic,
child: tree.toWidget(context),
);
diff --git a/lib/src/html_elements.dart b/lib/src/html_elements.dart
index ab1bb441fc..fd8a6660df 100644
--- a/lib/src/html_elements.dart
+++ b/lib/src/html_elements.dart
@@ -22,9 +22,6 @@ const STYLED_ELEMENTS = [
"kbd",
"mark",
"q",
- "rp",
- "rt",
- "ruby",
"s",
"samp",
"small",
@@ -85,6 +82,9 @@ const REPLACED_ELEMENTS = [
"svg",
"template",
"video",
+ "rp",
+ "rt",
+ "ruby",
];
const LAYOUT_ELEMENTS = [
diff --git a/lib/src/replaced_element.dart b/lib/src/replaced_element.dart
index 3e6ba39d87..e819a5b291 100644
--- a/lib/src/replaced_element.dart
+++ b/lib/src/replaced_element.dart
@@ -1,4 +1,5 @@
import 'dart:convert';
+import 'dart:math';
import 'package:chewie/chewie.dart';
import 'package:chewie_audio/chewie_audio.dart';
@@ -19,14 +20,19 @@ import 'package:html/dom.dart' as dom;
/// A [ReplacedElement] may use its children nodes to determine relevant information
/// (e.g. 's tags), but the children nodes will not be saved as [children].
abstract class ReplacedElement extends StyledElement {
- ReplacedElement({
- String name,
- Style style,
- dom.Element node,
- }) : super(name: name, children: null, style: style, node: node);
+ PlaceholderAlignment alignment;
+
+ ReplacedElement(
+ {String name,
+ Style style,
+ dom.Element node,
+ this.alignment = PlaceholderAlignment.aboveBaseline})
+ : super(name: name, children: null, style: style, node: node);
static List parseMediaSources(List elements) {
- return elements.where((element) => element.localName == 'source').map((element) {
+ return elements
+ .where((element) => element.localName == 'source')
+ .map((element) {
return element.attributes['src'];
}).toList();
}
@@ -68,7 +74,8 @@ class ImageContentElement extends ReplacedElement {
@override
Widget toWidget(RenderContext context) {
- if (src == null) return Text(alt ?? "", style: context.style.generateTextStyle());
+ if (src == null)
+ return Text(alt ?? "", style: context.style.generateTextStyle());
if (src.startsWith("data:image") && src.contains("base64,")) {
return Image.memory(base64.decode(src.split("base64,")[1].trim()));
} else {
@@ -110,7 +117,9 @@ class IframeContentElement extends ReplacedElement {
child: WebView(
initialUrl: src,
javascriptMode: JavascriptMode.unrestricted,
- gestureRecognizers: {Factory(() => PlatformViewVerticalGestureRecognizer())},
+ gestureRecognizers: {
+ Factory(() => PlatformViewVerticalGestureRecognizer())
+ },
),
);
}
@@ -189,7 +198,9 @@ class VideoContentElement extends ReplacedElement {
videoPlayerController: VideoPlayerController.network(
src.first ?? "",
),
- placeholder: poster != null ? Image.network(poster) : Container(color: Colors.black),
+ placeholder: poster != null
+ ? Image.network(poster)
+ : Container(color: Colors.black),
autoPlay: autoplay,
looping: loop,
showControls: showControls,
@@ -229,6 +240,55 @@ class EmptyContentElement extends ReplacedElement {
Widget toWidget(_) => null;
}
+class RubyElement extends ReplacedElement {
+ dom.Element element;
+
+ RubyElement({@required this.element, String name = "ruby"})
+ : super(name: name, alignment: PlaceholderAlignment.middle);
+
+ @override
+ Widget toWidget(RenderContext context) {
+ dom.Node textNode = null;
+ List widgets = List();
+ final rubySize = max(9.0, context.style.fontSize / 2);
+ final rubyYPos = rubySize + 2;
+ element.nodes.forEach((c) {
+ if (c.nodeType == dom.Node.TEXT_NODE) {
+ textNode = c;
+ }
+ if (c is dom.Element) {
+ if (c.localName == "rt" && textNode != null) {
+ final widget = Stack(
+ alignment: Alignment.center,
+ children: [
+ Container(
+ alignment: Alignment.bottomCenter,
+ child: Center(
+ child: Transform(
+ transform:
+ Matrix4.translationValues(0, -(rubyYPos), 0),
+ child: Text(c.innerHtml,
+ style: context.style
+ .generateTextStyle()
+ .copyWith(fontSize: rubySize))))),
+ Container(
+ child: Text(textNode.text.trim(),
+ style: context.style.generateTextStyle())),
+ ],
+ );
+ widgets.add(widget);
+ }
+ }
+ });
+ return Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ textBaseline: TextBaseline.alphabetic,
+ mainAxisSize: MainAxisSize.min,
+ children: widgets,
+ );
+ }
+}
+
ReplacedElement parseReplacedElement(dom.Element element) {
switch (element.localName) {
case "audio":
@@ -287,6 +347,10 @@ ReplacedElement parseReplacedElement(dom.Element element) {
width: double.tryParse(element.attributes['width'] ?? ""),
height: double.tryParse(element.attributes['height'] ?? ""),
);
+ case "ruby":
+ return RubyElement(
+ element: element,
+ );
default:
return EmptyContentElement(name: element.localName);
}