Skip to content

Commit e427ed8

Browse files
committed
WIP: margin/width processing
1 parent 65c434d commit e427ed8

File tree

7 files changed

+187
-54
lines changed

7 files changed

+187
-54
lines changed

lib/custom_render.dart

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class SelectableCustomRender extends CustomRender {
108108
}) : super.inlineSpan(inlineSpan: null);
109109
}
110110

111-
CustomRender blockElementRender({Style? style, List<InlineSpan>? children}) =>
111+
CustomRender blockElementRender({Style? style, List<InlineSpan>? children, required Size containingBlockSize}) =>
112112
CustomRender.inlineSpan(inlineSpan: (context, buildChildren) {
113113
if (context.parser.selectable) {
114114
return TextSpan(
@@ -132,6 +132,7 @@ CustomRender blockElementRender({Style? style, List<InlineSpan>? children}) =>
132132
renderContext: context,
133133
style: style ?? context.tree.style,
134134
shrinkWrap: context.parser.shrinkWrap,
135+
containingBlockSize: containingBlockSize,
135136
children: children ??
136137
context.tree.children
137138
.expandIndexed((i, childTree) => [
@@ -147,14 +148,15 @@ CustomRender blockElementRender({Style? style, List<InlineSpan>? children}) =>
147148
});
148149

149150
CustomRender listElementRender(
150-
{Style? style, Widget? child, List<InlineSpan>? children}) =>
151+
{Style? style, Widget? child, List<InlineSpan>? children, required Size containingBlockSize}) =>
151152
CustomRender.inlineSpan(
152153
inlineSpan: (context, buildChildren) => WidgetSpan(
153154
child: ContainerSpan(
154155
key: context.key,
155156
renderContext: context,
156157
style: style ?? context.tree.style,
157158
shrinkWrap: context.parser.shrinkWrap,
159+
containingBlockSize: containingBlockSize,
158160
child: Row(
159161
crossAxisAlignment: CrossAxisAlignment.start,
160162
mainAxisSize: MainAxisSize.min,
@@ -503,19 +505,21 @@ CustomRender fallbackRender({Style? style, List<InlineSpan>? children}) =>
503505
.toList(),
504506
));
505507

506-
final Map<CustomRenderMatcher, CustomRender> defaultRenders = {
507-
blockElementMatcher(): blockElementRender(),
508-
listElementMatcher(): listElementRender(),
509-
textContentElementMatcher(): textContentElementRender(),
510-
dataUriMatcher(): base64ImageRender(),
511-
assetUriMatcher(): assetImageRender(),
512-
networkSourceMatcher(): networkImageRender(),
513-
replacedElementMatcher(): replacedElementRender(),
514-
interactableElementMatcher(): interactableElementRender(),
515-
layoutElementMatcher(): layoutElementRender(),
516-
verticalAlignMatcher(): verticalAlignRender(),
517-
fallbackMatcher(): fallbackRender(),
518-
};
508+
Map<CustomRenderMatcher, CustomRender> generateDefaultRenders(Size containingBlockSize) {
509+
return {
510+
blockElementMatcher(): blockElementRender(containingBlockSize: containingBlockSize),
511+
listElementMatcher(): listElementRender(containingBlockSize: containingBlockSize),
512+
textContentElementMatcher(): textContentElementRender(),
513+
dataUriMatcher(): base64ImageRender(),
514+
assetUriMatcher(): assetImageRender(),
515+
networkSourceMatcher(): networkImageRender(),
516+
replacedElementMatcher(): replacedElementRender(),
517+
interactableElementMatcher(): interactableElementRender(),
518+
layoutElementMatcher(): layoutElementRender(),
519+
verticalAlignMatcher(): verticalAlignRender(),
520+
fallbackMatcher(): fallbackRender(),
521+
};
522+
}
519523

520524
List<InlineSpan> _getListElementChildren(
521525
ListStylePosition? position, Function() buildChildren) {

lib/flutter_html.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ class _HtmlState extends State<Html> {
194194
style: widget.style,
195195
customRenders: {}
196196
..addAll(widget.customRenders)
197-
..addAll(defaultRenders),
197+
..addAll(generateDefaultRenders(MediaQuery.of(context).size)),
198198
tagsList: widget.tagsList.isEmpty ? Html.tags : widget.tagsList,
199199
constraints: constraints,
200200
);
@@ -367,7 +367,7 @@ class _SelectableHtmlState extends State<SelectableHtml> {
367367
style: widget.style,
368368
customRenders: {}
369369
..addAll(widget.customRenders)
370-
..addAll(defaultRenders),
370+
..addAll(generateDefaultRenders(MediaQuery.of(context).size)),
371371
tagsList:
372372
widget.tagsList.isEmpty ? SelectableHtml.tags : widget.tagsList,
373373
selectionControls: widget.selectionControls,

lib/html_parser.dart

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ class HtmlParser extends StatelessWidget {
375375
style: tree.style,
376376
shrinkWrap: newContext.parser.shrinkWrap,
377377
child: customRenders[entry]!.widget!.call(newContext, buildChildren),
378+
containingBlockSize: tree.containingBlockSize,
378379
),
379380
);
380381
}
@@ -877,6 +878,7 @@ class ContainerSpan extends StatelessWidget {
877878
final Style style;
878879
final RenderContext renderContext;
879880
final bool shrinkWrap;
881+
final Size containingBlockSize;
880882

881883
ContainerSpan({
882884
this.key,
@@ -885,43 +887,25 @@ class ContainerSpan extends StatelessWidget {
885887
required this.style,
886888
required this.renderContext,
887889
this.shrinkWrap = false,
890+
required this.containingBlockSize,
888891
}): super(key: key);
889892

890893
@override
891894
Widget build(BuildContext context) {
892895

893-
// Elements that are inline should ignore margin: auto for alignment.
894-
var alignment = shrinkWrap ? null : style.alignment;
895-
896-
// TODO(Sub6Resources): This needs to follow the CSS spec for computing auto values!
897-
if(style.display == Display.BLOCK) {
898-
if(style.margin?.left?.unit == Unit.auto && style.margin?.right?.unit == Unit.auto)
899-
alignment = Alignment.bottomCenter;
900-
else if(style.margin?.left?.unit == Unit.auto)
901-
alignment = Alignment.bottomRight;
902-
}
903-
904-
//TODO(Sub6Resources): Is there a better value?
905-
double emValue = (style.fontSize?.size ?? 16) *
906-
MediaQuery.of(context).devicePixelRatio *
907-
MediaQuery.of(context).textScaleFactor;
896+
//Calculate auto widths and margins:
897+
final widthsAndMargins = WidthAndMargins.calculate(style, containingBlockSize, context);
908898

909899
Widget container = Container(
910900
decoration: BoxDecoration(
911901
border: style.border,
912902
color: style.backgroundColor,
913903
),
914904
height: style.height,
915-
width: style.width,
905+
width: style.width, //widthsAndMargins.width,
916906
padding: style.padding?.nonNegative,
917-
//TODO GIVE A VALID AUTO VALUE, maybe move this all to a new method?
918-
margin: EdgeInsets.only(
919-
left: computeDimensionValue(style.margin?.left ?? Margin.zero(), DimensionComputeContext(emValue: emValue, autoValue: 0)),
920-
right: computeDimensionValue(style.margin?.right ?? Margin.zero(), DimensionComputeContext(emValue: emValue, autoValue: 0)),
921-
bottom: computeDimensionValue(style.margin?.bottom ?? Margin.zero(), DimensionComputeContext(emValue: emValue, autoValue: 0)),
922-
top: computeDimensionValue(style.margin?.top ?? Margin.zero(), DimensionComputeContext(emValue: emValue, autoValue: 0)),
923-
),
924-
alignment: alignment,
907+
margin: widthsAndMargins.margins,
908+
alignment: shrinkWrap ? null : style.alignment,
925909
child: child ??
926910
StyledText(
927911
textSpan: TextSpan(

lib/src/replaced_element.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class RubyElement extends ReplacedElement {
112112
tree: c,
113113
),
114114
style: c.style,
115+
containingBlockSize: containingBlockSize,
115116
child: Text(c.element!.innerHtml,
116117
style: c.style
117118
.generateTextStyle()
@@ -120,6 +121,7 @@ class RubyElement extends ReplacedElement {
120121
ContainerSpan(
121122
renderContext: context,
122123
style: context.style,
124+
containingBlockSize: containingBlockSize,
123125
child: node is TextContentElement ? Text((node as TextContentElement).text?.trim() ?? "",
124126
style: context.style.generateTextStyle()) : null,
125127
children: node is TextContentElement ? null : [context.parser.parseTree(context, node!)]),

lib/src/style/compute_style.dart

Lines changed: 154 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,163 @@
1-
import 'package:flutter_html/src/style/length.dart';
1+
import 'dart:math';
22

3-
class DimensionComputeContext {
4-
const DimensionComputeContext({
5-
required this.emValue,
6-
required this.autoValue,
7-
});
8-
9-
final double emValue;
10-
final double autoValue;
11-
}
3+
import 'package:flutter/material.dart';
4+
import 'package:flutter_html/style.dart';
125

136
/// [computeDimensionUnit] takes a [Dimension] and some information about the
147
/// context where the Dimension is being used, and returns a "used" value to
158
/// use in a rendering.
16-
double computeDimensionValue(Dimension dimension, DimensionComputeContext computeContext) {
9+
double _computeDimensionValue(Dimension dimension, double emValue, double autoValue) {
1710
switch (dimension.unit) {
18-
case Unit.em: return computeContext.emValue * dimension.value;
11+
case Unit.em: return emValue * dimension.value;
1912
case Unit.px: return dimension.value;
20-
case Unit.auto: return computeContext.autoValue;
13+
case Unit.auto: return autoValue;
14+
}
15+
}
16+
17+
double _calculateEmValue(Style style, BuildContext buildContext) {
18+
//TODO is there a better value for this?
19+
return (style.fontSize?.size ?? 16) *
20+
MediaQuery.textScaleFactorOf(buildContext) *
21+
MediaQuery.of(buildContext).devicePixelRatio;
22+
}
23+
24+
/// This class handles the calculation of widths and margins during parsing:
25+
/// See [calculate] within.
26+
class WidthAndMargins {
27+
final double? width;
28+
29+
final EdgeInsets margins;
30+
31+
const WidthAndMargins({required this.width, required this.margins});
32+
33+
/// [WidthsAndMargins.calculate] calculates any auto values ans resolves any
34+
/// overconstraint for various elements..
35+
/// See https://drafts.csswg.org/css2/#Computing_widths_and_margins
36+
static WidthAndMargins calculate(Style style, Size containingBlockSize, BuildContext buildContext) {
37+
38+
final emValue = _calculateEmValue(style, buildContext);
39+
40+
double? width;
41+
double marginLeft = _computeDimensionValue(style.margin?.left ?? Margin.zero(), emValue, 0);
42+
double marginRight = _computeDimensionValue(style.margin?.right ?? Margin.zero(), emValue, 0);
43+
44+
switch(style.display ?? Display.BLOCK) {
45+
case Display.BLOCK:
46+
47+
// TODO: Handle the case of determining the width of replaced block elements in normal flow
48+
// See https://drafts.csswg.org/css2/#block-replaced-width
49+
50+
bool autoWidth = style.width == null;
51+
bool autoMarginLeft = style.margin?.left?.unit == Unit.auto;
52+
bool autoMarginRight = style.margin?.right?.unit == Unit.auto;
53+
54+
double? overrideMarginLeft;
55+
double? overrideMarginRight;
56+
double? autoLeftMarginValue;
57+
double? autoRightMarginValue;
58+
final borderWidth = (style.border?.left.width ?? 0) + (style.border?.right.width ?? 0);
59+
final paddingWidth = (style.padding?.left ?? 0) + (style.padding?.right ?? 0);
60+
final nonAutoWidths = borderWidth + paddingWidth;
61+
final nonAutoMarginWidth = marginLeft + marginRight;
62+
63+
//If width is not auto, check the total width of the containing block:
64+
if(!autoWidth) {
65+
if(nonAutoWidths + style.width! + nonAutoMarginWidth > containingBlockSize.width) {
66+
autoLeftMarginValue = 0;
67+
autoRightMarginValue = 0;
68+
autoMarginLeft = false;
69+
autoMarginRight = false;
70+
}
71+
}
72+
73+
//If all values are explicit, the box is over-constrained, and we must
74+
//override one of the given margin values (left if the overconstrained
75+
//element has a rtl directionality, and right if the overconstrained
76+
//element has a ltr directionality). Margins must be non-negative in
77+
//Flutter, so we set them to 0 if they go below that.
78+
if(!autoWidth && !autoMarginLeft && !autoMarginRight) {
79+
final difference = containingBlockSize.width - (nonAutoWidths + style.width! + nonAutoMarginWidth);
80+
switch(style.direction) {
81+
case TextDirection.rtl:
82+
overrideMarginLeft = max(marginLeft + difference, 0);
83+
break;
84+
case TextDirection.ltr:
85+
overrideMarginRight = max(marginRight + difference, 0);
86+
break;
87+
case null:
88+
final directionality = Directionality.maybeOf(buildContext);
89+
if(directionality != null) {
90+
switch(directionality) {
91+
case TextDirection.rtl:
92+
overrideMarginLeft = max(marginLeft + difference, 0);
93+
break;
94+
case TextDirection.ltr:
95+
overrideMarginRight = max(marginRight + difference, 0);
96+
break;
97+
}
98+
} else {
99+
overrideMarginRight = max(marginRight + difference, 0);
100+
}
101+
}
102+
}
103+
104+
//If exactly one unit is auto, calculate it from the equality.
105+
if(autoWidth && !autoMarginLeft && !autoMarginRight) {
106+
width = containingBlockSize.width - (nonAutoWidths + nonAutoMarginWidth);
107+
} else if(!autoWidth && autoMarginLeft && !autoMarginRight) {
108+
overrideMarginLeft = containingBlockSize.width - (nonAutoWidths + style.width! + marginRight);
109+
} else if(!autoWidth && !autoMarginLeft && autoMarginRight) {
110+
overrideMarginRight = containingBlockSize.width - (nonAutoWidths + style.width! + marginLeft);
111+
}
112+
113+
//If width is auto, set all other auto values to 0, and the width is
114+
//calculated from the equality
115+
if(style.width == null) {
116+
autoLeftMarginValue = 0;
117+
autoRightMarginValue = 0;
118+
autoMarginLeft = false;
119+
autoMarginRight = false;
120+
width = containingBlockSize.width - (nonAutoMarginWidth + nonAutoWidths);
121+
}
122+
123+
//If margin-left and margin-right are both auto, their values are equal,
124+
// and the element is centered.
125+
if(autoMarginLeft && autoMarginRight) {
126+
final marginWidth = containingBlockSize.width - (nonAutoWidths + style.width!);
127+
overrideMarginLeft = marginWidth / 2;
128+
overrideMarginRight = marginWidth / 2;
129+
}
130+
131+
marginLeft = overrideMarginLeft ?? _computeDimensionValue(style.margin?.left ?? Margin.zero(), emValue, autoLeftMarginValue ?? 0);
132+
marginRight = overrideMarginRight ?? _computeDimensionValue(style.margin?.right ?? Margin.zero(), emValue, autoRightMarginValue ?? 0);
133+
break;
134+
case Display.INLINE:
135+
case Display.INLINE_BLOCK:
136+
137+
//All inline elements have a computed auto value for margin-left and right of 0.
138+
marginLeft = _computeDimensionValue(style.margin?.left ?? Margin.zero(), emValue, 0);
139+
marginRight = _computeDimensionValue(style.margin?.right ?? Margin.zero(), emValue, 0);
140+
141+
// TODO: Handle the case of replaced inline elements and intrinsic ratio
142+
// (See https://drafts.csswg.org/css2/#inline-replaced-width)
143+
break;
144+
case Display.LIST_ITEM:
145+
// TODO: Any handling for this case?
146+
break;
147+
case Display.NONE:
148+
// Do nothing
149+
break;
150+
}
151+
152+
return WidthAndMargins(
153+
width: width,
154+
margins: EdgeInsets.only(
155+
left: marginLeft,
156+
right: marginRight,
157+
top: _computeDimensionValue(style.margin?.top ?? Margin.zero(), emValue, 0),
158+
bottom: _computeDimensionValue(style.margin?.bottom ?? Margin.zero(), emValue, 0),
159+
),
160+
);
21161
}
162+
22163
}

packages/flutter_html_iframe/lib/iframe_mobile.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ CustomRender iframeRender({NavigationDelegate? navigationDelegate}) =>
1818
child: ContainerSpan(
1919
style: context.style,
2020
renderContext: context,
21+
containingBlockSize: Size.zero, //TODO this is incorrect
2122
child: WebView(
2223
initialUrl: context.tree.element?.attributes['src'],
2324
key: key,

packages/flutter_html_iframe/lib/iframe_web.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ CustomRender iframeRender({NavigationDelegate? navigationDelegate}) =>
3939
child: ContainerSpan(
4040
style: context.style,
4141
renderContext: context,
42+
containingBlockSize: Size.zero, //TODO this is incorrect
4243
child: Directionality(
4344
textDirection: TextDirection.ltr,
4445
child: HtmlElementView(

0 commit comments

Comments
 (0)