Skip to content

Commit 32bf16c

Browse files
committed
Update FontSize definition and fix bugs with CSSBoxWidget
1 parent ed75550 commit 32bf16c

File tree

19 files changed

+281
-183
lines changed

19 files changed

+281
-183
lines changed

example/lib/main.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ class _MyHomePageState extends State<MyHomePage> {
307307
? FlutterLogoStyle.horizontal
308308
: FlutterLogoStyle.markOnly,
309309
textColor: context.style.color!,
310-
size: context.style.fontSize!.size! * 5,
310+
size: context.style.fontSize!.value * 5,
311311
)),
312312
tagMatcher("table"): CustomRender.widget(widget: (context, buildChildren) => SingleChildScrollView(
313313
scrollDirection: Axis.horizontal,

lib/custom_render.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'dart:convert';
66
import 'package:flutter/gestures.dart';
77
import 'package:flutter/material.dart';
88
import 'package:flutter_html/flutter_html.dart';
9-
import 'package:flutter_html/src/css_box_widget.dart';
109
import 'package:flutter_html/src/html_elements.dart';
1110
import 'package:flutter_html/src/utils.dart';
1211

@@ -576,9 +575,9 @@ final _dataUriFormat = RegExp(
576575
double _getVerticalOffset(StyledElement tree) {
577576
switch (tree.style.verticalAlign) {
578577
case VerticalAlign.SUB:
579-
return tree.style.fontSize!.size! / 2.5;
578+
return tree.style.fontSize!.value / 2.5;
580579
case VerticalAlign.SUPER:
581-
return tree.style.fontSize!.size! / -2.5;
580+
return tree.style.fontSize!.value / -2.5;
582581
default:
583582
return 0;
584583
}

lib/flutter_html.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export 'package:flutter_html/src/interactable_element.dart';
1818
export 'package:flutter_html/src/layout_element.dart';
1919
export 'package:flutter_html/src/replaced_element.dart';
2020
export 'package:flutter_html/src/styled_element.dart';
21+
//export css_box_widget for use in custom render.
22+
export 'package:flutter_html/src/css_box_widget.dart';
2123
//export style api
2224
export 'package:flutter_html/style.dart';
2325

lib/html_parser.dart

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'package:csslib/parser.dart' as cssparser;
66
import 'package:csslib/visitor.dart' as css;
77
import 'package:flutter/material.dart';
88
import 'package:flutter_html/flutter_html.dart';
9-
import 'package:flutter_html/src/css_box_widget.dart';
109
import 'package:flutter_html/src/css_parser.dart';
1110
import 'package:flutter_html/src/html_elements.dart';
1211
import 'package:flutter_html/src/utils.dart';
@@ -88,7 +87,7 @@ class HtmlParser extends StatelessWidget {
8887
StyledElement styledTree = styleTree(lexedTree, htmlData, style, onCssParseError);
8988

9089
// Processing Step
91-
StyledElement processedTree = processTree(styledTree);
90+
StyledElement processedTree = processTree(styledTree, MediaQuery.of(context).devicePixelRatio);
9291

9392
// Parsing Step
9493
InlineSpan parsedTree = parseTree(
@@ -301,14 +300,15 @@ class HtmlParser extends StatelessWidget {
301300
/// [processTree] optimizes the [StyledElement] tree so all [BlockElement]s are
302301
/// on the first level, redundant levels are collapsed, empty elements are
303302
/// removed, and specialty elements are processed.
304-
static StyledElement processTree(StyledElement tree) {
303+
static StyledElement processTree(StyledElement tree, double devicePixelRatio) {
305304
tree = _processInternalWhitespace(tree);
306305
tree = _processInlineWhitespace(tree);
307306
tree = _removeEmptyElements(tree);
307+
308+
tree = _calculateRelativeValues(tree, devicePixelRatio);
308309
tree = _processListCharacters(tree);
309310
tree = _processBeforesAndAfters(tree);
310311
tree = _collapseMargins(tree);
311-
tree = _processFontSize(tree);
312312
return tree;
313313
}
314314

@@ -648,7 +648,7 @@ class HtmlParser extends StatelessWidget {
648648
return tree;
649649
}
650650

651-
/// [collapseMargins] follows the specifications at https://www.w3.org/TR/CSS21/box.html#collapsing-margins
651+
/// [collapseMargins] follows the specifications at https://www.w3.org/TR/CSS22/box.html#collapsing-margins
652652
/// for collapsing margins of block-level boxes. This prevents the doubling of margins between
653653
/// boxes, and makes for a more correct rendering of the html content.
654654
///
@@ -662,7 +662,7 @@ class HtmlParser extends StatelessWidget {
662662
//Short circuit if we've reached a leaf of the tree
663663
if (tree.children.isEmpty) {
664664
// Handle case (4) from above.
665-
if ((tree.style.height ?? 0) == 0) {
665+
if (tree.style.height?.value == 0 && tree.style.height?.unit != Unit.auto) {
666666
tree.style.margin = tree.style.margin?.collapse() ?? Margins.zero;
667667
}
668668
return tree;
@@ -729,7 +729,7 @@ class HtmlParser extends StatelessWidget {
729729
final previousSiblingBottom =
730730
tree.children[i - 1].style.margin?.bottom?.value ?? 0;
731731
final thisTop = tree.children[i].style.margin?.top?.value ?? 0;
732-
final newInternalMargin = max(previousSiblingBottom, thisTop) / 2;
732+
final newInternalMargin = max(previousSiblingBottom, thisTop);
733733

734734
if (tree.children[i - 1].style.margin == null) {
735735
tree.children[i - 1].style.margin =
@@ -798,21 +798,64 @@ class HtmlParser extends StatelessWidget {
798798
return tree;
799799
}
800800

801-
/// [_processFontSize] changes percent-based font sizes (negative numbers in this implementation)
802-
/// to pixel-based font sizes.
803-
static StyledElement _processFontSize(StyledElement tree) {
804-
double? parentFontSize = tree.style.fontSize?.size ?? FontSize.medium.size;
801+
/// [_calculateRelativeValues] converts rem values to px sizes and then
802+
/// applies relative calculations
803+
static StyledElement _calculateRelativeValues(StyledElement tree, double devicePixelRatio) {
804+
double remSize = (tree.style.fontSize?.value ?? FontSize.medium.value);
805+
806+
//If the root element has a rem-based fontSize, then give it the default
807+
// font size times the set rem value.
808+
if(tree.style.fontSize?.unit == Unit.rem) {
809+
tree.style.fontSize = FontSize(FontSize.medium.value * remSize);
810+
}
811+
812+
_applyRelativeValuesRecursive(tree, remSize, devicePixelRatio);
813+
tree.style.setRelativeValues(remSize, remSize / devicePixelRatio);
814+
815+
return tree;
816+
}
817+
818+
/// This is the recursive worker function for [_calculateRelativeValues]
819+
static void _applyRelativeValuesRecursive(StyledElement tree, double remFontSize, double devicePixelRatio) {
820+
//When we get to this point, there should be a valid fontSize at every level.
821+
assert(tree.style.fontSize != null);
822+
823+
final parentFontSize = tree.style.fontSize!.value;
805824

806825
tree.children.forEach((child) {
807-
if ((child.style.fontSize?.size ?? parentFontSize)! < 0) {
808-
child.style.fontSize =
809-
FontSize(parentFontSize! * -child.style.fontSize!.size!);
826+
827+
if(child.style.fontSize == null) {
828+
child.style.fontSize = FontSize(parentFontSize);
829+
} else {
830+
switch(child.style.fontSize!.unit) {
831+
case Unit.em:
832+
child.style.fontSize = FontSize(parentFontSize * child.style.fontSize!.value);
833+
break;
834+
case Unit.percent:
835+
child.style.fontSize = FontSize(parentFontSize * (child.style.fontSize!.value / 100.0));
836+
break;
837+
case Unit.rem:
838+
child.style.fontSize = FontSize(remFontSize * child.style.fontSize!.value);
839+
break;
840+
case Unit.px:
841+
case Unit.auto:
842+
//Ignore
843+
break;
844+
}
810845
}
811846

812-
_processFontSize(child);
847+
// Note: it is necessary to scale down the emSize by the factor of
848+
// devicePixelRatio since Flutter seems to calculates font sizes using
849+
// physical pixels, but margins/padding using logical pixels.
850+
final emSize = child.style.fontSize!.value / devicePixelRatio;
851+
852+
tree.style.setRelativeValues(remFontSize, emSize);
853+
854+
_applyRelativeValuesRecursive(child, remFontSize, devicePixelRatio);
813855
});
814-
return tree;
815856
}
857+
858+
816859
}
817860

818861
/// The [RenderContext] is available when parsing the tree. It contains information

lib/src/css_box_widget.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class CSSBoxWidget extends StatelessWidget {
6767
paddingSize: style.padding?.collapsedSize ?? Size.zero,
6868
borderSize: style.border?.dimensions.collapsedSize ?? Size.zero,
6969
margins: style.margin ?? Margins.zero,
70-
display: style.display ?? Display.BLOCK,
70+
display: style.display ?? Display.INLINE,
7171
childIsReplaced: childIsReplaced,
7272
emValue: _calculateEmValue(style, context),
7373
textDirection: _checkTextDirection(context, textDirection),
@@ -691,14 +691,15 @@ extension Normalize on Dimension {
691691
return;
692692
case Unit.px:
693693
case Unit.auto:
694+
case Unit.percent:
694695
return;
695696
}
696697
}
697698
}
698699

699700
double _calculateEmValue(Style style, BuildContext buildContext) {
700701
//TODO is there a better value for this?
701-
return (style.fontSize?.size ?? 16) *
702+
return (style.fontSize?.emValue ?? 16) *
702703
MediaQuery.textScaleFactorOf(buildContext) *
703704
MediaQuery.of(buildContext).devicePixelRatio;
704705
}

lib/src/css_parser.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -620,15 +620,15 @@ class ExpressionMapping {
620620

621621
static FontSize? expressionToFontSize(css.Expression value) {
622622
if (value is css.NumberTerm) {
623-
return FontSize(double.tryParse(value.text));
623+
return FontSize(double.tryParse(value.text) ?? 16, Unit.px);
624624
} else if (value is css.PercentageTerm) {
625-
return FontSize.percent(double.tryParse(value.text)!);
625+
return FontSize(double.tryParse(value.text) ?? 100, Unit.percent);
626626
} else if (value is css.EmTerm) {
627-
return FontSize.em(double.tryParse(value.text));
628-
} else if (value is css.RemTerm) {
629-
return FontSize.rem(double.tryParse(value.text)!);
627+
return FontSize(double.tryParse(value.text) ?? 1, Unit.em);
628+
// } else if (value is css.RemTerm) { TODO
629+
// return FontSize.rem(double.tryParse(value.text) ?? 1, Unit.em);
630630
} else if (value is css.LengthTerm) {
631-
return FontSize(double.tryParse(value.text.replaceAll(new RegExp(r'\s+(\d+\.\d+)\s+'), '')));
631+
return FontSize(double.tryParse(value.text.replaceAll(new RegExp(r'\s+(\d+\.\d+)\s+'), '')) ?? 16);
632632
} else if (value is css.LiteralTerm) {
633633
switch (value.text) {
634634
case "xx-small":

lib/src/replaced_element.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class RubyElement extends ReplacedElement {
7878
Widget toWidget(RenderContext context) {
7979
StyledElement? node;
8080
List<Widget> widgets = <Widget>[];
81-
final rubySize = context.parser.style['rt']?.fontSize?.size ?? max(9.0, context.style.fontSize!.size! / 2);
81+
final rubySize = context.parser.style['rt']?.fontSize?.value ?? max(9.0, context.style.fontSize!.value / 2);
8282
final rubyYPos = rubySize + rubySize / 2;
8383
List<StyledElement> children = [];
8484
context.tree.children.forEachIndexed((index, element) {

lib/src/style/fontsize.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//TODO implement dimensionality
2+
3+
import 'length.dart';
4+
5+
class FontSize extends LengthOrPercent {
6+
7+
FontSize(double size, [Unit unit = Unit.px]): super(size, unit);
8+
9+
// These values are calculated based off of the default (`medium`)
10+
// being 14px.
11+
//
12+
// TODO(Sub6Resources): This seems to override Flutter's accessibility text scaling.
13+
//
14+
// Negative values are computed during parsing to be a percentage of
15+
// the parent style's font size.
16+
static final xxSmall = FontSize(7.875);
17+
static final xSmall = FontSize(8.75);
18+
static final small = FontSize(11.375);
19+
static final medium = FontSize(14.0);
20+
static final large = FontSize(15.75);
21+
static final xLarge = FontSize(21.0);
22+
static final xxLarge = FontSize(28.0);
23+
static final smaller = FontSize(83, Unit.percent);
24+
static final larger = FontSize(120, Unit.percent);
25+
26+
static FontSize? inherit(FontSize? parent, FontSize? child) {
27+
if(child != null && parent != null) {
28+
if(child.unit == Unit.em) {
29+
return FontSize(child.value * parent.value);
30+
} else if(child.unit == Unit.percent) {
31+
return FontSize(child.value / 100.0 * parent.value);
32+
}
33+
return child;
34+
}
35+
36+
return parent;
37+
}
38+
39+
double get emValue => this.value;
40+
}

lib/src/style/length.dart

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1+
/// Increase new base unit types' values by a factor of 2 each time.
12
const int _percent = 0x1;
23
const int _length = 0x2;
34
const int _auto = 0x4;
5+
6+
/// These values are combinations of the base unit-types
47
const int _lengthPercent = _length | _percent;
58
const int _lengthPercentAuto = _lengthPercent | _auto;
69

7-
//TODO there are more unit-types that need support
10+
/// A Unit represents a CSS unit
811
enum Unit {
912
//ch,
1013
em(_length),
1114
//ex,
12-
//percent(_percent),
15+
percent(_percent),
1316
px(_length),
14-
//rem,
17+
rem(_length),
1518
//Q,
1619
//vh,
1720
//vw,
@@ -25,29 +28,27 @@ abstract class Dimension {
2528
double value;
2629
Unit unit;
2730

28-
Dimension(this.value, this.unit) {
29-
assert((unit.unitType | _unitType) == _unitType, "You used a unit for the property that was not allowed");
30-
}
31-
32-
int get _unitType;
31+
Dimension(this.value, this.unit, int _dimensionUnitType)
32+
: assert(identical((unit.unitType | _dimensionUnitType), _dimensionUnitType),
33+
"This dimension was given a Unit that isn't specified.");
3334
}
3435

36+
/// This dimension takes a value with a length unit such as px or em. Note that
37+
/// these can be fixed or relative (but they must not be a percent)
3538
class Length extends Dimension {
36-
Length(double value, [Unit unit = Unit.px]) : super(value, unit);
37-
38-
@override
39-
int get _unitType => _length;
39+
Length(double value, [Unit unit = Unit.px]):
40+
super(value, unit, _length);
4041
}
4142

43+
/// This dimension takes a value with a length-percent unit such as px or em
44+
/// or %. Note that these can be fixed or relative (but they must not be a
45+
/// percent)
4246
class LengthOrPercent extends Dimension {
43-
LengthOrPercent(double value, [Unit unit = Unit.px]) : super(value, unit);
44-
45-
@override
46-
int get _unitType => _lengthPercent;
47+
LengthOrPercent(double value, [Unit unit = Unit.px]):
48+
super(value, unit, _lengthPercent);
4749
}
4850

4951
class AutoOrLengthOrPercent extends Dimension {
50-
AutoOrLengthOrPercent(double value, [Unit unit = Unit.px]): super(value, unit);
51-
52-
int get _unitType => _lengthPercentAuto;
52+
AutoOrLengthOrPercent(double value, [Unit unit = Unit.px]):
53+
super(value, unit, _lengthPercentAuto);
5354
}

lib/src/style/lineheight.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//TODO implement dimensionality
2+
class LineHeight {
3+
final double? size;
4+
final String units;
5+
6+
const LineHeight(this.size, {this.units = ""});
7+
8+
factory LineHeight.percent(double percent) {
9+
return LineHeight(percent / 100.0 * 1.2, units: "%");
10+
}
11+
12+
factory LineHeight.em(double em) {
13+
return LineHeight(em * 1.2, units: "em");
14+
}
15+
16+
factory LineHeight.rem(double rem) {
17+
return LineHeight(rem * 1.2, units: "rem");
18+
}
19+
20+
factory LineHeight.number(double num) {
21+
return LineHeight(num * 1.2, units: "number");
22+
}
23+
24+
static const normal = LineHeight(1.2);
25+
}

0 commit comments

Comments
 (0)