Skip to content

Commit 62743d8

Browse files
authored
Update README to add customImageRender details
1 parent 3cfb18e commit 62743d8

File tree

1 file changed

+179
-1
lines changed

1 file changed

+179
-1
lines changed

README.md

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ A Flutter widget for rendering HTML and CSS as Flutter widgets.
6565

6666
- [Example](#example-usage---navigationdelegateforiframe)
6767

68+
- [customImageRender](#customimagerender)
69+
70+
- [typedef ImageSourceMatcher (with examples)](#typedef-imagesourcematcher)
71+
72+
- [typedef ImageRender (with examples)](#typedef-imagerender)
73+
74+
- [Examples](#example-usages---customimagerender)
75+
6876
- [Rendering Reference](#rendering-reference)
6977

7078
- [Image](#image)
@@ -140,6 +148,7 @@ Below, you will find brief descriptions of the parameters the`Html` widget accep
140148
| `blacklistedElements` | A list of elements the `Html` widget should not render. The list should contain the tags of the HTML elements you wish to blacklist. |
141149
| `style` | A powerful API that allows you to customize the style that should be used when rendering a specific HTMl tag. |
142150
| `navigationDelegateForIframe` | Allows you to set the `NavigationDelegate` for the `WebView`s of all the iframes rendered by the `Html` widget. |
151+
| `customImageRender` | A powerful API that allows you to fully customize how images are loaded. |
143152

144153
### Data:
145154

@@ -267,7 +276,7 @@ Widget html = Html(
267276
}
268277
}
269278
}
270-
),
279+
);
271280
```
272281
</details>
273282

@@ -405,6 +414,175 @@ Widget html = Html(
405414
);
406415
```
407416

417+
### customImageRender:
418+
419+
A powerful API that allows you to customize what the `Html` widget does when rendering an image, down to the most minute detail.
420+
421+
`customImageRender` accepts a `Map<ImageSourceMatcher, ImageRender>`. `ImageSourceMatcher` provides the matching function, while `ImageRender` provides the widget to be rendered.
422+
423+
The default image renders are:
424+
425+
```dart
426+
final Map<ImageSourceMatcher, ImageRender> defaultImageRenders = {
427+
base64UriMatcher(): base64ImageRender(),
428+
networkSourceMatcher(extension: "svg"): svgNetworkImageRender(),
429+
networkSourceMatcher(): networkImageRender(),
430+
};
431+
```
432+
433+
See [the source code](https://github.com/Sub6Resources/flutter_html/blob/master/lib/image_render.dart) for details on how these are implemented.
434+
435+
When setting `customImageRenders`, the package will prioritize the custom renders first, while the default ones are used as a fallback.
436+
437+
Note: Order is very important when you set `customImageRenders`. The more specific your `ImageSourceMatcher`, the higher up in the `customImageRender` list it should be.
438+
439+
#### typedef ImageSourceMatcher
440+
441+
This is type defined as a function that passes the attributes as a `Map<String, String>` and the DOM element as `dom.Element`. This type is used to define how an image should be matched i.e. whether the package should override the default rendering method and instead use your custom implementation.
442+
443+
A typical usage would look something like this:
444+
445+
```dart
446+
ImageSourceMatcher base64UriMatcher() => (attributes, element) =>
447+
attributes["src"].startsWith("data:image") &&
448+
attributes["src"].contains("base64,");
449+
```
450+
451+
In the above example, the matcher checks whether the image's `src` either starts with "data:image" or contains "base64,", since these indicate an image in base64 format.
452+
453+
You can also declare your own variables in the function itself, which would look like this:
454+
455+
```dart
456+
ImageSourceMatcher networkSourceMatcher({
457+
/// all three are optional, you don't need to have these in the function
458+
List<String> schemas: const ["https", "http"],
459+
List<String> domains: const ["your domain 1", "your domain 2"],
460+
String extension: "your extension",
461+
}) =>
462+
(attributes, element) {
463+
final src = Uri.parse(attributes["src"]);
464+
return schemas.contains(src.scheme) &&
465+
(domains == null || domains.contains(src.host)) &&
466+
(extension == null || src.path.endsWith(".$extension"));
467+
};
468+
```
469+
470+
In the above example, the possible schemas are checked against the scheme of the `src`, and optionally the domains and extensions are also checked. This implementation allows for extremely granular control over what images are matched, and could even be changed on the fly with a variable.
471+
472+
#### typedef ImageRender
473+
474+
This is a type defined as a function that passes the attributes of the image as a `Map<String, String>`, the current [`RenderContext`](https://github.com/Sub6Resources/flutter_html/wiki/All-About-customRender#rendercontext-context), and the DOM element as `dom.Element`. This type is used to define the widget that should be rendered when used in conjunction with an `ImageSourceMatcher`.
475+
476+
A typical usage might look like this:
477+
478+
```dart
479+
ImageRender base64ImageRender() => (context, attributes, element) {
480+
final decodedImage = base64.decode(attributes["src"].split("base64,")[1].trim());
481+
return Image.memory(
482+
decodedImage,
483+
);
484+
};
485+
```
486+
487+
The above example should be used with the `base64UriMatcher()` in the examples for `ImageSourceMatcher`.
488+
489+
Just like functions for `ImageSourceMatcher`, you can declare your own variables in the function itself:
490+
491+
```dart
492+
ImageRender networkImageRender({
493+
Map<String, String> headers,
494+
double width,
495+
double height,
496+
Widget Function(String) altWidget,
497+
}) =>
498+
(context, attributes, element) {
499+
return Image.network(
500+
attributes["src"],
501+
headers: headers,
502+
width: width,
503+
height: height,
504+
frameBuilder: (ctx, child, frame, _) {
505+
if (frame == null) {
506+
return altWidget.call(attributes["alt"]) ??
507+
Text(attributes["alt"] ?? "",
508+
style: context.style.generateTextStyle());
509+
}
510+
return child;
511+
},
512+
);
513+
};
514+
```
515+
516+
Implementing these variables allows you to customize every last detail of how the widget is rendered.
517+
518+
#### Example Usages - customImageRender:
519+
520+
`customImageRender` can be used in two different ways:
521+
522+
1. Overriding a default render:
523+
```dart
524+
Widget html = Html(
525+
data: """
526+
<img alt='Flutter' src='https://flutter.dev/assets/flutter-lockup-1caf6476beed76adec3c477586da54de6b552b2f42108ec5bc68dc63bae2df75.png' /><br />
527+
<img alt='Google' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' /><br />
528+
""",
529+
customImageRenders: {
530+
networkSourceMatcher(domains: ["flutter.dev"]):
531+
(context, attributes, element) {
532+
return FlutterLogo(size: 36);
533+
},
534+
networkSourceMatcher(): networkImageRender(
535+
headers: {"Custom-Header": "some-value"},
536+
altWidget: (alt) => Text(alt),
537+
),
538+
},
539+
);
540+
```
541+
542+
Above, there are two `networkSourceMatcher`s. One is overriden to only apply to images on the URL `flutter.dev`, while the other covers the rest of the cases.
543+
When an image with URL `flutter.dev` is detected, rather than displaying the image, the render will display the flutter logo. If the image is any other image, it keeps the default widget, but just sets the headers and the alt text in case that image happens to be broken.
544+
545+
2. Creating your own renders:
546+
```dart
547+
ImageSourceMatcher classAndIdMatcher({String classToMatch, String idToMatch}) => (attributes, element) =>
548+
attributes["class"].contains(classToMatch) ||
549+
attributes["id"].contains(idToMatch);
550+
551+
ImageRender classAndIdRender({String classToMatch, String idToMatch}) => (context, attributes, element) {
552+
if (attributes["class"].contains(classToMatch)) {
553+
return Image.asset(attributes["src"]);
554+
} else {
555+
return Image.network(
556+
attributes["src"],
557+
semanticLabel: attributes["longdesc"],
558+
width: attributes["width"],
559+
height: attributes["height"],
560+
color: context.style.color,
561+
frameBuilder: (ctx, child, frame, _) {
562+
if (frame == null) {
563+
return Text(attributes["alt"] ?? "", style: context.style.generateTextStyle());
564+
}
565+
return child;
566+
},
567+
);
568+
}
569+
};
570+
571+
Widget html = Html(
572+
data: """
573+
<img alt='alt text' class='class1-class2' src='assets/flutter.png' /><br />
574+
<img alt='alt text 2' id='imageId' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' /><br />
575+
""",
576+
customImageRenders: {
577+
classAndIdMatcher(classToMatch: "class1", idToMatch: "imageId"): classAndIdRender(classToMatch: "class1", idToMatch: "imageId")
578+
},
579+
);
580+
```
581+
582+
The above example has a matcher that checks for either a class or an id, and then returns two different widgets based on whether a class was matched or an id was matched.
583+
584+
The sky is the limit when using the custom image renders. You can make it as granular as you want, or as all-encompassing as you want, and you have full control of everything. Plus you get the package's style parsing to use in your custom widgets, so your code looks neat and readable!
585+
408586
## Rendering Reference
409587

410588
This section will describe how certain HTML elements are rendered by this package, so you can evaluate how your HTML will be rendered and structure it accordingly.

0 commit comments

Comments
 (0)