@@ -5,11 +5,34 @@ import 'package:flutter_html/flutter_html.dart';
55import 'package:flutter_html/src/tree/image_element.dart' ;
66
77class ImageBuiltIn extends Extension {
8+ final String ? dataEncoding;
9+ final Set <String >? mimeTypes;
810 final Map <String , String >? networkHeaders;
11+ final Set <String > networkSchemas;
12+ final Set <String >? networkDomains;
13+ final Set <String >? fileExtensions;
14+
15+ final String assetSchema;
16+ final AssetBundle ? assetBundle;
17+ final String ? assetPackage;
18+
19+ final bool handleNetworkImages;
20+ final bool handleAssetImages;
21+ final bool handleDataImages;
922
10- //TODO how can the end user access this?
1123 const ImageBuiltIn ({
1224 this .networkHeaders,
25+ this .networkDomains,
26+ this .networkSchemas = const {"http" , "https" },
27+ this .fileExtensions,
28+ this .assetSchema = "asset:" ,
29+ this .assetBundle,
30+ this .assetPackage,
31+ this .mimeTypes,
32+ this .dataEncoding,
33+ this .handleNetworkImages = true ,
34+ this .handleAssetImages = true ,
35+ this .handleDataImages = true ,
1336 });
1437
1538 @override
@@ -23,30 +46,9 @@ class ImageBuiltIn extends Extension {
2346 return false ;
2447 }
2548
26- if (context.attributes['src' ] == null ) {
27- return false ;
28- }
29-
30- final src = context.attributes['src' ]! ;
31-
32- // Data Image Schema:
33- final dataUri = dataUriFormat.firstMatch (src);
34- if (dataUri != null && dataUri.namedGroup ('mime' ) != "image/svg+xml" ) {
35- return true ;
36- }
37-
38- // Asset Image Schema:
39- if (src.startsWith ("asset:" ) && ! src.endsWith (".svg" )) {
40- return true ;
41- }
42-
43- // Network Image Schema:
44- try {
45- final srcUri = Uri .parse (src);
46- return ! srcUri.path.endsWith (".svg" );
47- } on FormatException {
48- return false ;
49- }
49+ return (_matchesNetworkImage (context) && handleNetworkImages) ||
50+ (_matchesAssetImage (context) && handleAssetImages) ||
51+ (_matchesBase64Image (context) && handleDataImages);
5052 }
5153
5254 @override
@@ -72,92 +74,125 @@ class ImageBuiltIn extends Extension {
7274 Map <StyledElement , InlineSpan > Function () parseChildren) {
7375 final element = context.styledElement as ImageElement ;
7476
75- final dataUri = dataUriFormat.firstMatch (element.src);
76- if (dataUri != null && dataUri.namedGroup ('mime' ) != "image/svg+xml" ) {
77- return WidgetSpan (
78- child: _base64ImageRender (context),
79- );
80- }
77+ final imageStyle = Style (
78+ width: element.width,
79+ height: element.height,
80+ ).merge (context.styledElement! .style);
8181
82- if (element.src.startsWith ("asset:" ) && ! element.src.endsWith (".svg" )) {
83- return WidgetSpan (
84- child: _assetImageRender (context),
85- );
82+ late Widget child;
83+ if (_matchesBase64Image (context)) {
84+ child = _base64ImageRender (context, imageStyle);
85+ } else if (_matchesAssetImage (context)) {
86+ child = _assetImageRender (context, imageStyle);
87+ } else if (_matchesNetworkImage (context)) {
88+ child = _networkImageRender (context, imageStyle);
89+ } else {
90+ // Our matcher went a little overboard and matched
91+ // something we can't render
92+ return TextSpan (text: element.alt);
8693 }
8794
88- try {
89- final srcUri = Uri .parse (element.src);
90- return WidgetSpan (
91- child: _networkImageRender (context, srcUri),
92- );
93- } on FormatException {
94- return const TextSpan (text: "" );
95- }
95+ return WidgetSpan (
96+ child: CssBoxWidget (
97+ style: imageStyle,
98+ childIsReplaced: true ,
99+ child: child,
100+ ),
101+ );
96102 }
97103
98104 static RegExp get dataUriFormat => RegExp (
99- r"^(?<scheme>data):(?<mime>image\ /[\w+\-.]+);(?<encoding>base64)?,\s*(?<data>.*)" );
105+ r"^(?<scheme>data):(?<mime>image/[\w+\-.]+);* (?<encoding>base64)?,\s*(?<data>.*)" );
100106
101- //TODO remove repeated code between these methods:
107+ bool _matchesBase64Image (ExtensionContext context) {
108+ final attributes = context.attributes;
102109
103- Widget _base64ImageRender (ExtensionContext context) {
110+ if (attributes['src' ] == null ) {
111+ return false ;
112+ }
113+
114+ final dataUri = dataUriFormat.firstMatch (attributes['src' ]! );
115+
116+ return context.elementName == "img" &&
117+ dataUri != null &&
118+ (mimeTypes == null ||
119+ mimeTypes! .contains (dataUri.namedGroup ('mime' ))) &&
120+ dataUri.namedGroup ('mime' ) != 'image/svg+xml' &&
121+ (dataEncoding == null ||
122+ dataUri.namedGroup ('encoding' ) == dataEncoding);
123+ }
124+
125+ bool _matchesAssetImage (ExtensionContext context) {
126+ final attributes = context.attributes;
127+
128+ return context.elementName == "img" &&
129+ attributes['src' ] != null &&
130+ ! attributes['src' ]! .endsWith (".svg" ) &&
131+ attributes['src' ]! .startsWith (assetSchema) &&
132+ (fileExtensions == null ||
133+ attributes['src' ]! .endsWithAnyFileExtension (fileExtensions! ));
134+ }
135+
136+ bool _matchesNetworkImage (ExtensionContext context) {
137+ final attributes = context.attributes;
138+
139+ if (attributes['src' ] == null ) {
140+ return false ;
141+ }
142+
143+ final src = Uri .tryParse (attributes['src' ]! );
144+ if (src == null ) {
145+ return false ;
146+ }
147+
148+ return context.elementName == "img" &&
149+ networkSchemas.contains (src.scheme) &&
150+ ! src.path.endsWith (".svg" ) &&
151+ (networkDomains == null || networkDomains! .contains (src.host)) &&
152+ (fileExtensions == null ||
153+ src.path.endsWithAnyFileExtension (fileExtensions! ));
154+ }
155+
156+ Widget _base64ImageRender (ExtensionContext context, Style imageStyle) {
104157 final element = context.styledElement as ImageElement ;
105158 final decodedImage = base64.decode (element.src.split ("base64," )[1 ].trim ());
106- final imageStyle = Style (
107- width: element.width,
108- height: element.height,
109- ).merge (context.styledElement! .style);
110159
111- return CssBoxWidget (
112- style: imageStyle,
113- childIsReplaced: true ,
114- child: Image .memory (
115- decodedImage,
116- width: imageStyle.width? .value,
117- height: imageStyle.height? .value,
118- fit: BoxFit .fill,
119- errorBuilder: (ctx, error, stackTrace) {
120- return Text (
121- element.alt ?? "" ,
122- style: context.styledElement! .style.generateTextStyle (),
123- );
124- },
125- ),
160+ return Image .memory (
161+ decodedImage,
162+ width: imageStyle.width? .value,
163+ height: imageStyle.height? .value,
164+ fit: BoxFit .fill,
165+ errorBuilder: (ctx, error, stackTrace) {
166+ return Text (
167+ element.alt ?? "" ,
168+ style: context.styledElement! .style.generateTextStyle (),
169+ );
170+ },
126171 );
127172 }
128173
129- Widget _assetImageRender (ExtensionContext context) {
174+ Widget _assetImageRender (ExtensionContext context, Style imageStyle ) {
130175 final element = context.styledElement as ImageElement ;
131176 final assetPath = element.src.replaceFirst ('asset:' , '' );
132- final imageStyle = Style (
133- width: element.width,
134- height: element.height,
135- ).merge (context.styledElement! .style);
136177
137- return CssBoxWidget (
138- style: imageStyle,
139- childIsReplaced: true ,
140- child: Image .asset (
141- assetPath,
142- width: imageStyle.width? .value,
143- height: imageStyle.height? .value,
144- fit: BoxFit .fill,
145- errorBuilder: (ctx, error, stackTrace) {
146- return Text (
147- element.alt ?? "" ,
148- style: context.styledElement! .style.generateTextStyle (),
149- );
150- },
151- ),
178+ return Image .asset (
179+ assetPath,
180+ width: imageStyle.width? .value,
181+ height: imageStyle.height? .value,
182+ fit: BoxFit .fill,
183+ bundle: assetBundle,
184+ package: assetPackage,
185+ errorBuilder: (ctx, error, stackTrace) {
186+ return Text (
187+ element.alt ?? "" ,
188+ style: context.styledElement! .style.generateTextStyle (),
189+ );
190+ },
152191 );
153192 }
154193
155- Widget _networkImageRender (ExtensionContext context, Uri srcUri ) {
194+ Widget _networkImageRender (ExtensionContext context, Style imageStyle ) {
156195 final element = context.styledElement as ImageElement ;
157- final imageStyle = Style (
158- width: element.width,
159- height: element.height,
160- ).merge (context.styledElement! .style);
161196
162197 return CssBoxWidget (
163198 style: imageStyle,
@@ -178,3 +213,14 @@ class ImageBuiltIn extends Extension {
178213 );
179214 }
180215}
216+
217+ extension _SetFolding on String {
218+ bool endsWithAnyFileExtension (Iterable <String > endings) {
219+ for (final element in endings) {
220+ if (endsWith (".$element " )) {
221+ return true ;
222+ }
223+ }
224+ return false ;
225+ }
226+ }
0 commit comments