Skip to content

Commit cdcd018

Browse files
committed
Merge branch 'master' of https://github.com/Sub6Resources/flutter_html into feature/selectable-text
� Conflicts: � lib/html_parser.dart
2 parents 42443ad + 2a21971 commit cdcd018

File tree

8 files changed

+145
-86
lines changed

8 files changed

+145
-86
lines changed

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ Add the following to your `pubspec.yaml` file:
105105
|------------|-----------|-------|-------------|---------|---------|-------|------|--------|--------|--------|
106106
|`a` | `abbr` | `acronym`| `address` | `article`| `aside` | `audio`| `b` | `bdi` | `bdo` | `big` |
107107
|`blockquote`| `body` | `br` | `caption` | `cite` | `code` | `data`| `dd` | `del` | `details` | `dfn` |
108-
| `div` | `dl` | `dt` | `em` | `figcaption`| `figure`| `footer`| `h1` | `h2` | `h3` | `h4` |
109-
| `h5` |`h6` | `header` | `hr` | `i` | `iframe`| `img` | `ins` | `kbd`| `li` | `main` |
110-
| `mark` | `nav` | `noscript`|`ol` | `p` | `pre` | `q` | `rp` | `rt` | `ruby` | `s` |
111-
| `samp` | `section` | `small` | `span`| `strike` | `strong`| `sub` | `sup` | `summary` | `svg`| `table`|
112-
| `tbody` | `td` | `template` | `tfoot` | `th` | `thead` |`time` | `tr` | `tt` | `u` | `ul` |
113-
| `var` | `video` | `math`: | `mrow` | `msup` | `msub` | `mover` | `munder` | `msubsup` | `moverunder` | `mfrac` |
114-
| `mlongdiv` | `msqrt` | `mroot` | `mi` | `mn` | `mo` | | | | | |
108+
| `div` | `dl` | `dt` | `em` | `figcaption`| `figure`| `footer`| `font` | `h1` | `h2` | `h3` |
109+
| `h4` | `h5` |`h6` | `header` | `hr` | `i` | `iframe`| `img` | `ins` | `kbd`| `li` |
110+
| `main` | `mark` | `nav` | `noscript`|`ol` | `p` | `pre` | `q` | `rp` | `rt` | `ruby` |
111+
| `s` | `samp` | `section` | `small` | `span`| `strike` | `strong`| `sub` | `sup` | `summary` | `svg`|
112+
| `table` | `tbody` | `td` | `template` | `tfoot` | `th` | `thead` |`time` | `tr` | `tt` | `u` |
113+
| `ul` | `var` | `video` | `math`: | `mrow` | `msup` | `msub` | `mover` | `munder` | `msubsup` | `moverunder` |
114+
| `mfrac` | `mlongdiv` | `msqrt` | `mroot` | `mi` | `mn` | `mo` | | | | |
115115

116116

117117
## Currently Supported CSS Attributes:

example/android/app/build.gradle

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ android {
3232
}
3333

3434
defaultConfig {
35-
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
3635
applicationId "com.example.example"
37-
minSdkVersion 16
36+
minSdkVersion 19
3837
targetSdkVersion 28
3938
versionCode flutterVersionCode.toInteger()
4039
versionName flutterVersionName
@@ -43,8 +42,6 @@ android {
4342

4443
buildTypes {
4544
release {
46-
// TODO: Add your own signing config for the release build.
47-
// Signing with the debug keys for now, so `flutter run --release` works.
4845
signingConfig signingConfigs.debug
4946
}
5047
}

lib/html_parser.dart

Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -304,28 +304,7 @@ class HtmlParser extends StatelessWidget {
304304
tree: tree,
305305
style: context.style.copyOnlyInherited(tree.style),
306306
);
307-
List<int> lineEndingIndices = [];
308-
tree.children.forEachIndexed((index, element) {
309-
//we want the element to be a block element, but we don't want to add
310-
//new-lines before/after the html and body
311-
if (element.style.display == Display.BLOCK
312-
&& element.element?.localName != "html"
313-
&& element.element?.localName != "body"
314-
) {
315-
//if the parent element is body and the element is first, we don't want
316-
//to add a new-line before
317-
if (index == 0 && element.element?.parent?.localName == "body") {
318-
lineEndingIndices.add(index + 1);
319-
} else {
320-
lineEndingIndices.addAll([index, index + 1]);
321-
}
322-
}
323-
});
324-
//we don't need a new-line at the end
325-
if (lineEndingIndices.isNotEmpty && lineEndingIndices.last == tree.children.length) {
326-
lineEndingIndices.removeLast();
327-
}
328-
lineEndingIndices = lineEndingIndices.toSet().toList();
307+
329308
if (customRender.containsKey(tree.name)) {
330309
final render = customRender[tree.name]!.call(
331310
newContext,
@@ -354,17 +333,26 @@ class HtmlParser extends StatelessWidget {
354333
}
355334

356335
//Return the correct InlineSpan based on the element type.
357-
if (tree.style.display == Display.BLOCK) {
336+
if (tree.style.display == Display.BLOCK && tree.children.isNotEmpty) {
358337
if (newContext.parser.selectable) {
359-
final children = tree.children.map((tree) => parseTree(newContext, tree)).toList();
360-
//use provided indices to insert new-lines at those locations
361-
//makes sure to account for list size changes with "+ i"
362-
lineEndingIndices.forEachIndexed((i, element) {
363-
children.insert(element + i, TextSpan(text: "\n"));
364-
});
365338
return TextSpan(
366339
style: newContext.style.generateTextStyle(),
367-
children: children,
340+
children: tree.children
341+
.expandIndexed((i, childTree) => [
342+
if (shrinkWrap &&
343+
childTree.style.display == Display.BLOCK &&
344+
i > 0 &&
345+
tree.children[i - 1] is ReplacedElement)
346+
TextSpan(text: "\n"),
347+
parseTree(newContext, childTree),
348+
if (shrinkWrap &&
349+
i != tree.children.length - 1 &&
350+
childTree.style.display == Display.BLOCK &&
351+
childTree.element?.localName != "html" &&
352+
childTree.element?.localName != "body")
353+
TextSpan(text: "\n"),
354+
])
355+
.toList(),
368356
);
369357
}
370358
return WidgetSpan(
@@ -373,7 +361,22 @@ class HtmlParser extends StatelessWidget {
373361
newContext: newContext,
374362
style: tree.style,
375363
shrinkWrap: context.parser.shrinkWrap,
376-
children: tree.children.map((tree) => parseTree(newContext, tree)).toList(),
364+
children: tree.children
365+
.expandIndexed((i, childTree) => [
366+
if (shrinkWrap &&
367+
childTree.style.display == Display.BLOCK &&
368+
i > 0 &&
369+
tree.children[i - 1] is ReplacedElement)
370+
TextSpan(text: "\n"),
371+
parseTree(newContext, childTree),
372+
if (shrinkWrap &&
373+
i != tree.children.length - 1 &&
374+
childTree.style.display == Display.BLOCK &&
375+
childTree.element?.localName != "html" &&
376+
childTree.element?.localName != "body")
377+
TextSpan(text: "\n"),
378+
])
379+
.toList(),
377380
),
378381
);
379382
} else if (tree.style.display == Display.LIST_ITEM) {
@@ -458,22 +461,17 @@ class HtmlParser extends StatelessWidget {
458461
);
459462
} else {
460463
return WidgetSpan(
461-
child: RawGestureDetector(
462-
key: AnchorKey.of(key, tree),
463-
gestures: {
464-
MultipleTapGestureRecognizer:
465-
GestureRecognizerFactoryWithHandlers<
466-
MultipleTapGestureRecognizer>(
467-
() => MultipleTapGestureRecognizer(),
468-
(instance) {
469-
instance
470-
..onTap = _onAnchorTap != null
471-
? () => _onAnchorTap!(tree.href, context, tree.attributes, tree.element)
472-
: null;
473-
},
474-
),
475-
},
476-
child: (childSpan as WidgetSpan).child,
464+
child: MultipleTapGestureDetector(
465+
onTap: _onAnchorTap != null
466+
? () => _onAnchorTap!(tree.href, context, tree.attributes, tree.element)
467+
: null,
468+
child: GestureDetector(
469+
key: AnchorKey.of(key, tree),
470+
onTap: _onAnchorTap != null
471+
? () => _onAnchorTap!(tree.href, context, tree.attributes, tree.element)
472+
: null,
473+
child: (childSpan as WidgetSpan).child,
474+
),
477475
),
478476
);
479477
}
@@ -512,21 +510,26 @@ class HtmlParser extends StatelessWidget {
512510
child: StyledText(
513511
textSpan: TextSpan(
514512
style: newContext.style.generateTextStyle(),
515-
children: tree.children
516-
.map((tree) => parseTree(newContext, tree))
517-
.toList(),
513+
children: tree.children.map((tree) => parseTree(newContext, tree)).toList(),
518514
),
519515
style: newContext.style,
520-
renderContext: context,
516+
renderContext: newContext,
521517
),
522518
),
523519
);
524520
} else {
525521
///[tree] is an inline element.
526522
return TextSpan(
527523
style: newContext.style.generateTextStyle(),
528-
children:
529-
tree.children.map((tree) => parseTree(newContext, tree)).toList(),
524+
children: tree.children
525+
.expand((tree) => [
526+
parseTree(newContext, tree),
527+
if (tree.style.display == Display.BLOCK &&
528+
tree.element?.localName != "html" &&
529+
tree.element?.localName != "body")
530+
TextSpan(text: "\n"),
531+
])
532+
.toList(),
530533
);
531534
}
532535
}
@@ -937,7 +940,7 @@ class StyledText extends StatelessWidget {
937940
);
938941
}
939942
return SizedBox(
940-
width: calculateWidth(style.display, renderContext),
943+
width: consumeExpandedBlock(style.display, renderContext),
941944
child: Text.rich(
942945
textSpan,
943946
style: style.generateTextStyle(),
@@ -950,13 +953,10 @@ class StyledText extends StatelessWidget {
950953
);
951954
}
952955

953-
double? calculateWidth(Display? display, RenderContext context) {
956+
double? consumeExpandedBlock(Display? display, RenderContext context) {
954957
if ((display == Display.BLOCK || display == Display.LIST_ITEM) && !renderContext.parser.shrinkWrap) {
955958
return double.infinity;
956959
}
957-
if (renderContext.parser.shrinkWrap) {
958-
return MediaQuery.of(context.buildContext).size.width;
959-
}
960960
return null;
961961
}
962962
}

lib/src/anchor.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,28 @@ import 'package:flutter/widgets.dart';
33
import 'package:flutter_html/src/styled_element.dart';
44

55
class AnchorKey extends GlobalKey {
6+
static final Set<AnchorKey> _registry = <AnchorKey>{};
7+
68
final Key parentKey;
79
final String id;
810

911
const AnchorKey._(this.parentKey, this.id) : super.constructor();
1012

1113
static AnchorKey? of(Key? parentKey, StyledElement? id) {
12-
return forId(parentKey, id?.elementId);
14+
final key = forId(parentKey, id?.elementId);
15+
if (key == null || _registry.contains(key)) {
16+
// Invalid id or already created a key with this id: silently ignore
17+
return null;
18+
}
19+
_registry.add(key);
20+
return key;
1321
}
1422

1523
static AnchorKey? forId(Key? parentKey, String? id) {
1624
if (parentKey == null || id == null || id.isEmpty || id == "[[No ID]]") {
1725
return null;
1826
}
27+
1928
return AnchorKey._(parentKey, id);
2029
}
2130

lib/src/replaced_element.dart

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,19 @@ class ImageContentElement extends ReplacedElement {
8383
for (final entry in context.parser.imageRenders.entries) {
8484
if (entry.key.call(attributes, element)) {
8585
final widget = entry.value.call(context, attributes, element);
86-
return RawGestureDetector(
87-
key: AnchorKey.of(context.parser.key, this),
88-
child: widget,
89-
gestures: {
90-
MultipleTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<MultipleTapGestureRecognizer>(
91-
() => MultipleTapGestureRecognizer(), (instance) {
92-
instance..onTap = () => context.parser.onImageTap?.call(src, context, attributes, element);
86+
return Builder(
87+
builder: (buildContext) {
88+
return GestureDetector(
89+
key: AnchorKey.of(context.parser.key, this),
90+
child: widget,
91+
onTap: () {
92+
if (MultipleTapGestureDetector.of(buildContext) != null) {
93+
MultipleTapGestureDetector.of(buildContext)!.onTap?.call();
94+
}
95+
context.parser.onImageTap?.call(src, context, attributes, element);
9396
},
94-
),
95-
},
97+
);
98+
}
9699
);
97100
}
98101
}
@@ -283,13 +286,13 @@ class MathElement extends ReplacedElement {
283286
required this.element,
284287
this.texStr,
285288
String name = "math",
286-
}) : super(name: name, alignment: PlaceholderAlignment.middle, style: Style(), elementId: element.id);
289+
}) : super(name: name, alignment: PlaceholderAlignment.middle, style: Style(display: Display.BLOCK), elementId: element.id);
287290

288291
@override
289292
Widget toWidget(RenderContext context) {
290293
texStr = parseMathRecursive(element, r'');
291294
return Container(
292-
width: MediaQuery.of(context.buildContext).size.width,
295+
width: context.parser.shrinkWrap ? null : MediaQuery.of(context.buildContext).size.width,
293296
child: Math.tex(
294297
texStr ?? '',
295298
mathStyle: MathStyle.display,

lib/src/styled_element.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:flutter/material.dart';
2+
import 'package:flutter_html/src/css_parser.dart';
23
import 'package:flutter_html/style.dart';
34
import 'package:html/dom.dart' as dom;
45
//TODO(Sub6Resources): don't use the internal code of the html package as it may change unexpectedly.
@@ -179,6 +180,17 @@ StyledElement parseStyledElement(
179180
display: Display.BLOCK,
180181
);
181182
break;
183+
case "font":
184+
styledElement.style = Style(
185+
color: element.attributes['color'] != null ?
186+
element.attributes['color']!.startsWith("#") ?
187+
ExpressionMapping.stringToColor(element.attributes['color']!) :
188+
ExpressionMapping.namedColorToColor(element.attributes['color']!) :
189+
null,
190+
fontFamily: element.attributes['face']?.split(",").first,
191+
fontSize: numberToFontSize(element.attributes['size'] ?? ''),
192+
);
193+
break;
182194
case "h1":
183195
styledElement.style = Style(
184196
fontSize: FontSize.xxLarge,
@@ -368,3 +380,31 @@ StyledElement parseStyledElement(
368380
}
369381

370382
typedef ListCharacter = String Function(int i);
383+
384+
FontSize numberToFontSize(String num) {
385+
switch (num) {
386+
case "1":
387+
return FontSize.xxSmall;
388+
case "2":
389+
return FontSize.xSmall;
390+
case "3":
391+
return FontSize.small;
392+
case "4":
393+
return FontSize.medium;
394+
case "5":
395+
return FontSize.large;
396+
case "6":
397+
return FontSize.xLarge;
398+
case "7":
399+
return FontSize.xxLarge;
400+
}
401+
if (num.startsWith("+")) {
402+
final relativeNum = double.tryParse(num.substring(1)) ?? 0;
403+
return numberToFontSize((3 + relativeNum).toString());
404+
}
405+
if (num.startsWith("-")) {
406+
final relativeNum = double.tryParse(num.substring(1)) ?? 0;
407+
return numberToFontSize((3 - relativeNum).toString());
408+
}
409+
return FontSize.medium;
410+
}

lib/src/utils.dart

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,21 @@ class Context<T> {
4848

4949
// This class is a workaround so that both an image
5050
// and a link can detect taps at the same time.
51-
class MultipleTapGestureRecognizer extends TapGestureRecognizer {
52-
@override
53-
void rejectGesture(int pointer) {
54-
acceptGesture(pointer);
51+
class MultipleTapGestureDetector extends InheritedWidget {
52+
final void Function()? onTap;
53+
54+
const MultipleTapGestureDetector({
55+
Key? key,
56+
required Widget child,
57+
required this.onTap,
58+
}) : super(key: key, child: child);
59+
60+
static MultipleTapGestureDetector? of(BuildContext context) {
61+
return context.dependOnInheritedWidgetOfExactType<MultipleTapGestureDetector>();
5562
}
63+
64+
@override
65+
bool updateShouldNotify(MultipleTapGestureDetector oldWidget) => false;
5666
}
5767

5868
class CustomBorderSide {

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: flutter_html
22
description: A Flutter widget rendering static HTML and CSS as Flutter widgets.
3-
version: 2.0.0-nullsafety.1
3+
version: 2.0.0
44
homepage: https://github.com/Sub6Resources/flutter_html
55

66
environment:

0 commit comments

Comments
 (0)