I am doing an experiment where the goal is to render Flutter Dart code into HTML.
import 'package:flutter/material.dart';
class HtmlLikeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('HTML-like Elements in Flutter'),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// H1
Text(
'This is H1',
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
// H2
Text(
'This is H2',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
// H3
Text(
'This is H3',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
// H4
Text(
'This is H4',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
// Paragraph with strong and em
RichText(
text: TextSpan(
text: 'This is a paragraph with ',
style: TextStyle(fontSize: 16, color: Colors.black),
children: <TextSpan>[
TextSpan(
text: 'strong',
style: TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(text: ' and '),
TextSpan(
text: 'emphasized',
style: TextStyle(fontStyle: FontStyle.italic),
),
TextSpan(text: ' text.'),
],
),
),
SizedBox(height: 10),
// Blockquote
Container(
padding: EdgeInsets.all(16.0),
color: Colors.grey[200],
child: Text(
'This is a blockquote. It is used to indicate the quotation of a large section of text from another source.',
style: TextStyle(fontSize: 16, fontStyle: FontStyle.italic),
),
),
SizedBox(height: 10),
// Unordered List
Text(
'Unordered List:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BulletPoint(text: 'First item'),
BulletPoint(text: 'Second item'),
BulletPoint(text: 'Third item'),
],
),
SizedBox(height: 10),
// Ordered List
Text(
'Ordered List:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
NumberedPoint(number: 1, text: 'First item'),
NumberedPoint(number: 2, text: 'Second item'),
NumberedPoint(number: 3, text: 'Third item'),
],
),
SizedBox(height: 10),
// Image
Image.network(
'https://via.placeholder.com/150',
height: 150,
width: 150,
),
SizedBox(height: 10),
// URL/Link
GestureDetector(
onTap: () {
// Handle URL tap
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Text('Link tapped!'),
);
},
);
},
child: Text(
'Click here to open a URL',
style: TextStyle(fontSize: 16, color: Colors.blue),
),
),
SizedBox(height: 10),
// Code Block
Container(
padding: EdgeInsets.all(8.0),
color: Colors.black,
child: Text(
'print("Hello, World!");',
style: TextStyle(fontSize: 16, color: Colors.green, fontFamily: 'Courier'),
),
),
],
),
),
);
}
}
class BulletPoint extends StatelessWidget {
final String text;
BulletPoint({required this.text});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'• ',
style: TextStyle(fontSize: 16),
),
Expanded(
child: Text(
text,
style: TextStyle(fontSize: 16),
),
),
],
),
);
}
}
class NumberedPoint extends StatelessWidget {
final int number;
final String text;
NumberedPoint({required this.number, required this.text});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$number. ',
style: TextStyle(fontSize: 16),
),
Expanded(
child: Text(
text,
style: TextStyle(fontSize: 16),
),
),
],
),
);
}
}
I know flutter web has an html
web-renderer.
fvm flutter build web --web-renderer html --csp
It renders the following Html code.
<flt-scene-host style="pointer-events: none;">
<flt-scene style="position: absolute;">
<flt-transform style="position: absolute; transform-origin: 0px 0px 0px; transform: none;">
<flt-clip clip-type="rect" style="position: absolute; left: 0px; top: 0px; width: 629px; height: 993px; overflow: hidden; z-index: 0;">
<flt-clip-interior style="position: absolute; left: 0px; top: 0px;">
<flt-offset style="position: absolute; transform-origin: 0px 0px 0px; transform: translate(0px, 0px);">
<flt-picture aria-hidden="true" style="position: absolute; transform: translate(0px, 0px);"></flt-picture>
<flt-offset style="position: absolute; transform-origin: 0px 0px 0px; transform: translate(0px, 0px);">
<flt-offset style="position: absolute; transform-origin: 0px 0px 0px; transform: translate(0px, 56px);">
<flt-offset style="position: absolute; transform-origin: 0px 0px 0px; transform: translate(0px, 0px);">
<flt-offset style="position: absolute; transform-origin: 0px 0px 0px; transform: translate(0px, 0px);">
<flt-picture aria-hidden="true" style="position: absolute; transform: translate(0px, 0px);">
<flt-canvas style="position: absolute; transform: translate(15px, 15px);">
<flt-paragraph style="position: absolute; white-space: pre; transform-origin: 0px 0px 0px; transform: matrix(1, 0, 0, 1, 1, 1); left: 0px; top: 0px;">
<flt-span style="color: rgb(255, 255, 255); font-size: 32px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 0px; width: 70.12px; line-height: 45.75px;">This</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 32px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 70.12px; width: 9.63px; line-height: 45.75px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 32px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 79.75px; width: 27px; line-height: 45.75px;">is</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 32px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 106.75px; width: 9.63px; line-height: 45.75px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 32px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 116.38px; width: 39.6px; line-height: 45.75px;">H1</flt-span>
</flt-paragraph>
<flt-paragraph style="position: absolute; white-space: pre; transform-origin: 0px 0px 0px; transform: matrix(1, 0, 0, 1, 1, 56.75); left: 0px; top: 0px;">
<flt-span style="color: rgb(255, 255, 255); font-size: 28px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 0px; width: 61.48px; line-height: 40.0312px;">This</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 28px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 61.48px; width: 8.45px; line-height: 40.0312px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 28px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 69.93px; width: 23.68px; line-height: 40.0312px;">is</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 28px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 93.61px; width: 8.45px; line-height: 40.0312px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 28px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 102.06px; width: 40.4px; line-height: 40.0312px;">H2</flt-span>
</flt-paragraph>
<flt-paragraph style="position: absolute; white-space: pre; transform-origin: 0px 0px 0px; transform: matrix(1, 0, 0, 1, 1, 106.781); left: 0px; top: 0px;">
<flt-span style="color: rgb(255, 255, 255); font-size: 24px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 0px; width: 52.84px; line-height: 34.3125px;">This</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 24px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 52.84px; width: 7.28px; line-height: 34.3125px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 24px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 60.12px; width: 20.37px; line-height: 34.3125px;">is</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 24px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 80.49px; width: 7.28px; line-height: 34.3125px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 24px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 87.77px; width: 34.8px; line-height: 34.3125px;">H3</flt-span>
</flt-paragraph>
<flt-paragraph style="position: absolute; white-space: pre; transform-origin: 0px 0px 0px; transform: matrix(1, 0, 0, 1, 1, 151.094); left: 0px; top: 0px;">
<flt-span style="color: rgb(255, 255, 255); font-size: 20px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 0px; width: 44.2px; line-height: 28.5938px;">This</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 20px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 44.2px; width: 6.11px; line-height: 28.5938px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 20px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 50.31px; width: 17.06px; line-height: 28.5938px;">is</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 20px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 67.37px; width: 6.11px; line-height: 28.5938px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 20px; font-weight: bold; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 73.48px; width: 30.32px; line-height: 28.5938px;">H4</flt-span>
</flt-paragraph>
<flt-paragraph style="position: absolute; white-space: pre; transform-origin: 0px 0px 0px; transform: matrix(1, 0, 0, 1, 1, 189.688); left: 0px; top: 0px;">
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 0px; width: 30.23px; line-height: 18px;">This</flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 30.23px; width: 4.45px; line-height: 18px;"> </flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 34.68px; width: 11.55px; line-height: 18px;">is</flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 46.23px; width: 4.45px; line-height: 18px;"> </flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 50.68px; width: 8.9px; line-height: 18px;">a</flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 59.58px; width: 4.45px; line-height: 18px;"> </flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 64.03px; width: 72.95px; line-height: 18px;">paragraph</flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 136.98px; width: 4.45px; line-height: 18px;"> </flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 141.43px; width: 28.45px; line-height: 18px;">with</flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 169.88px; width: 4.45px; line-height: 18px;"> </flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-weight: bold; font-family: sans-serif; position: absolute; top: 0px; left: 174.33px; width: 49.77px; line-height: 18px;">strong</flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 224.1px; width: 4.45px; line-height: 18px;"> </flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 228.55px; width: 26.7px; line-height: 18px;">and</flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 255.25px; width: 4.45px; line-height: 18px;"> </flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-style: italic; font-family: sans-serif; position: absolute; top: 0px; left: 259.7px; width: 86.27px; line-height: 18px;">emphasized</flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 345.97px; width: 4.45px; line-height: 18px;"> </flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 350.42px; width: 25.79px; line-height: 18px;">text</flt-span>
<flt-span style="color: rgb(0, 0, 0); font-size: 16px; font-family: sans-serif; position: absolute; top: 0px; left: 376.21px; width: 4.45px; line-height: 18px;">.</flt-span>
</flt-paragraph>
<draw-rect style="position: absolute; transform-origin: 0px 0px 0px; transform: matrix(1, 0, 0, 1, 1, 217.688); width: 597px; height: 77.75px; background-color: rgb(238, 238, 238);"></draw-rect>
<flt-paragraph style="position: absolute; white-space: pre; transform-origin: 0px 0px 0px; transform: matrix(1, 0, 0, 1, 17, 233.688); left: 0px; top: 0px;">
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 0px; width: 35.56px; line-height: 22.875px;">This</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 35.56px; width: 4.94px; line-height: 22.875px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 40.5px; width: 13.75px; line-height: 22.875px;">is</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 54.25px; width: 4.94px; line-height: 22.875px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 59.19px; width: 11.08px; line-height: 22.875px;">a</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 70.27px; width: 4.94px; line-height: 22.875px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 75.21px; width: 96.58px; line-height: 22.875px;">blockquote</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 171.79px; width: 8.64px; line-height: 22.875px;">. </flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 180.43px; width: 11.38px; line-height: 22.875px;">It</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 191.81px; width: 4.94px; line-height: 22.875px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 196.75px; width: 13.75px; line-height: 22.875px;">is</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 210.5px; width: 4.94px; line-height: 22.875px;">
</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 215.44px; width: 41.27px; line-height: 22.875px;">used</flt-span>
<flt-span style="color: rgb(255, 255, 255); font-size: 16px; font-weight: normal; font-style: italic; font-family: Comfortaa_regular, -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: 0.25px; position: absolute; top: 0px; left: 256.71px; width: 4.94px; line-height: 22.875px;">
</flt-span>
Now the next step of my challenge is to use this LinkText
widget and do whatever it takes to render it as real HTML Links. (The <a></a>
tag )
class LinkText extends StatelessWidget {
final String text;
final bool? isHovered;
final FontWeight? fontWeight;
final double? fontSize;
final Color hoveredColor;
final Color color;
final bool underline;
final GestureTapCallback? onTap;
final TextOverflow? overflow;
const LinkText(
this.text, {
Key? key,
this.isHovered,
this.fontWeight,
this.onTap,
this.overflow,
this.hoveredColor = Colors.blue,
this.color = Colors.white,
this.underline = false,
this.fontSize,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: HoverBuilder(builder: (hovering) {
final isHovered = this.isHovered ?? hovering;
return Text(
text,
overflow: overflow,
style: TextStyle(
fontSize: this.fontSize ?? 13,
fontFamily: 'Recoleta',
fontWeight: fontWeight ?? FontWeight.w200,
color: isHovered ? hoveredColor : color,
decoration: underline ? TextDecoration.underline : null,
),
);
}),
);
}
}
If I were to do it manually, my plan is to write a Javascript code that watches the DOM for some identifier (that I can lets the Javascript code know that this html block is supposed to be an Html Link), find a way to extract and parse the URL link, and wrap the generated element into the <a></a>
tag but I have two issues.
-
How pass such identifier to rendered HTML code ?
-
Is this approach over-complicated ? Is there a built-in Flutter Web hidden feature that can make my life easier ?