Skip to content

Commit 822d16e

Browse files
committed
Add support for auto horizontal margins
Allow centering images with auto-margins
1 parent d5a2751 commit 822d16e

File tree

7 files changed

+923
-404
lines changed

7 files changed

+923
-404
lines changed

example/lib/main.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ const htmlData = r"""
5757
<p style="text-align: right;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
5858
<p style="text-align: justify;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
5959
<p style="text-align: center;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
60+
<div style="width: 150px; height: 20px; background-color: red; margin: auto;">Centered Div</div>
61+
<div style="width: 150px; height: 20px; background-color: yellow; margin-left: auto;">Margin-left auto Div</div>
62+
<img style="margin: auto;" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png">
6063
<h3>Table support (with custom styling!):</h3>
6164
<p>
6265
<q>Famous quote...</q>
@@ -272,8 +275,7 @@ class _MyHomePageState extends State<MyHomePage> {
272275
"table": (context, child) {
273276
return SingleChildScrollView(
274277
scrollDirection: Axis.horizontal,
275-
child:
276-
(context.tree as TableLayoutElement).toWidget(context),
278+
child: (context.tree as TableLayoutElement).toWidget(context),
277279
);
278280
},
279281
"bird": (RenderContext context, Widget child) {
@@ -294,8 +296,7 @@ class _MyHomePageState extends State<MyHomePage> {
294296
(context, attributes, element) {
295297
return FlutterLogo(size: 36);
296298
},
297-
networkSourceMatcher(domains: ["mydomain.com"]):
298-
networkImageRender(
299+
networkSourceMatcher(domains: ["mydomain.com"]): networkImageRender(
299300
headers: {"Custom-Header": "some-value"},
300301
altWidget: (alt) => Text(alt ?? ""),
301302
loadingWidget: () => Text("Loading..."),

lib/html_parser.dart

Lines changed: 257 additions & 136 deletions
Large diffs are not rendered by default.

lib/image_render.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ ImageRender networkImageRender({
148148
initialData: context.parser.cachedImageSizes[src],
149149
builder: (BuildContext buildContext, AsyncSnapshot<Size> snapshot) {
150150
if (snapshot.hasData) {
151-
return Container(
151+
var imageWidget = Container(
152152
constraints: BoxConstraints(
153153
maxWidth: width ?? _width(attributes) ?? snapshot.data!.width,
154154
maxHeight:
@@ -172,6 +172,13 @@ ImageRender networkImageRender({
172172
),
173173
),
174174
);
175+
if (context.style.margin?.isAutoHorizontal ?? false) {
176+
return Align(
177+
alignment: context.style.margin!.alignment,
178+
child: imageWidget,
179+
);
180+
}
181+
return imageWidget;
175182
} else if (snapshot.hasError) {
176183
return altWidget?.call(_alt(attributes)) ??
177184
Text(_alt(attributes) ?? "",

lib/src/css_parser.dart

Lines changed: 502 additions & 209 deletions
Large diffs are not rendered by default.

lib/src/layout_element.dart

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ abstract class LayoutElement extends StyledElement {
1818
required List<StyledElement> children,
1919
String? elementId,
2020
dom.Element? node,
21-
}) : super(name: name, children: children, style: Style(), node: node, elementId: elementId ?? "[[No ID]]");
21+
}) : super(
22+
name: name,
23+
children: children,
24+
style: Style(),
25+
node: node,
26+
elementId: elementId ?? "[[No ID]]");
2227

2328
Widget? toWidget(RenderContext context);
2429
}
@@ -34,16 +39,16 @@ class TableLayoutElement extends LayoutElement {
3439
Widget toWidget(RenderContext context) {
3540
return Container(
3641
key: AnchorKey.of(context.parser.key, this),
42+
margin: style.margin?.asInsets.nonNegative ?? EdgeInsets.zero,
3743
padding: style.padding?.nonNegative,
38-
margin: style.margin?.nonNegative,
39-
alignment: style.alignment,
4044
decoration: BoxDecoration(
4145
color: style.backgroundColor,
4246
border: style.border,
4347
),
4448
width: style.width,
4549
height: style.height,
46-
child: LayoutBuilder(builder: (_, constraints) => _layoutCells(context, constraints)),
50+
child: LayoutBuilder(
51+
builder: (_, constraints) => _layoutCells(context, constraints)),
4752
);
4853
}
4954

@@ -89,18 +94,23 @@ class TableLayoutElement extends LayoutElement {
8994
}
9095

9196
// All table rows have a height intrinsic to their (spanned) contents
92-
final rowSizes = List.generate(rows.length, (_) => IntrinsicContentTrackSize());
97+
final rowSizes =
98+
List.generate(rows.length, (_) => IntrinsicContentTrackSize());
9399

94100
// Calculate column bounds
95101
int columnMax = 0;
96102
List<int> rowSpanOffsets = [];
97103
for (final row in rows) {
98-
final cols = row.children.whereType<TableCellElement>().fold(0, (int value, child) => value + child.colspan) +
104+
final cols = row.children
105+
.whereType<TableCellElement>()
106+
.fold(0, (int value, child) => value + child.colspan) +
99107
rowSpanOffsets.fold<int>(0, (int offset, child) => child);
100108
columnMax = max(cols, columnMax);
101109
rowSpanOffsets = [
102110
...rowSpanOffsets.map((value) => value - 1).where((value) => value > 0),
103-
...row.children.whereType<TableCellElement>().map((cell) => cell.rowspan - 1),
111+
...row.children
112+
.whereType<TableCellElement>()
113+
.map((cell) => cell.rowspan - 1),
104114
];
105115
}
106116

@@ -112,19 +122,21 @@ class TableLayoutElement extends LayoutElement {
112122
for (var row in rows) {
113123
int columni = 0;
114124
for (var child in row.children) {
115-
if (columni > columnMax - 1 ) {
125+
if (columni > columnMax - 1) {
116126
break;
117127
}
118128
if (child is TableCellElement) {
119129
while (columnRowOffset[columni] > 0) {
120130
columnRowOffset[columni] = columnRowOffset[columni] - 1;
121-
columni += columnColspanOffset[columni].clamp(1, columnMax - columni - 1);
131+
columni +=
132+
columnColspanOffset[columni].clamp(1, columnMax - columni - 1);
122133
}
123134
cells.add(GridPlacement(
124135
child: Container(
125136
width: child.style.width ?? double.infinity,
126137
height: child.style.height,
127-
padding: child.style.padding?.nonNegative ?? row.style.padding?.nonNegative,
138+
padding: child.style.padding?.nonNegative ??
139+
row.style.padding?.nonNegative,
128140
decoration: BoxDecoration(
129141
color: child.style.backgroundColor ?? row.style.backgroundColor,
130142
border: child.style.border ?? row.style.border,
@@ -217,7 +229,13 @@ class TableCellElement extends StyledElement {
217229
required List<StyledElement> children,
218230
required Style style,
219231
required dom.Element node,
220-
}) : super(name: name, elementId: elementId, elementClasses: elementClasses, children: children, style: style, node: node) {
232+
}) : super(
233+
name: name,
234+
elementId: elementId,
235+
elementClasses: elementClasses,
236+
children: children,
237+
style: style,
238+
node: node) {
221239
colspan = _parseSpan(this, "colspan");
222240
rowspan = _parseSpan(this, "rowspan");
223241
}
@@ -292,42 +310,55 @@ class DetailsContentElement extends LayoutElement {
292310

293311
@override
294312
Widget toWidget(RenderContext context) {
295-
List<InlineSpan>? childrenList = children.map((tree) => context.parser.parseTree(context, tree)).toList();
313+
List<InlineSpan>? childrenList = children
314+
.map((tree) => context.parser.parseTree(context, tree))
315+
.toList();
296316
List<InlineSpan> toRemove = [];
297317
for (InlineSpan child in childrenList) {
298-
if (child is TextSpan && child.text != null && child.text!.trim().isEmpty) {
318+
if (child is TextSpan &&
319+
child.text != null &&
320+
child.text!.trim().isEmpty) {
299321
toRemove.add(child);
300322
}
301323
}
302324
for (InlineSpan child in toRemove) {
303325
childrenList.remove(child);
304326
}
305-
InlineSpan? firstChild = childrenList.isNotEmpty == true ? childrenList.first : null;
327+
InlineSpan? firstChild =
328+
childrenList.isNotEmpty == true ? childrenList.first : null;
306329
return ExpansionTile(
307330
key: AnchorKey.of(context.parser.key, this),
308331
expandedAlignment: Alignment.centerLeft,
309-
title: elementList.isNotEmpty == true && elementList.first.localName == "summary" ? StyledText(
310-
textSpan: TextSpan(
311-
style: style.generateTextStyle(),
312-
children: firstChild == null ? [] : [firstChild],
313-
),
314-
style: style,
315-
renderContext: context,
316-
) : Text("Details"),
332+
title: elementList.isNotEmpty == true &&
333+
elementList.first.localName == "summary"
334+
? StyledText(
335+
textSpan: TextSpan(
336+
style: style.generateTextStyle(),
337+
children: firstChild == null ? [] : [firstChild],
338+
),
339+
style: style,
340+
renderContext: context,
341+
)
342+
: Text("Details"),
317343
children: [
318344
StyledText(
319345
textSpan: TextSpan(
320-
style: style.generateTextStyle(),
321-
children: getChildren(childrenList, context, elementList.isNotEmpty == true && elementList.first.localName == "summary" ? firstChild : null)
322-
),
346+
style: style.generateTextStyle(),
347+
children: getChildren(
348+
childrenList,
349+
context,
350+
elementList.isNotEmpty == true &&
351+
elementList.first.localName == "summary"
352+
? firstChild
353+
: null)),
323354
style: style,
324355
renderContext: context,
325356
),
326-
]
327-
);
357+
]);
328358
}
329359

330-
List<InlineSpan> getChildren(List<InlineSpan> children, RenderContext context, InlineSpan? firstChild) {
360+
List<InlineSpan> getChildren(List<InlineSpan> children, RenderContext context,
361+
InlineSpan? firstChild) {
331362
if (firstChild != null) children.removeAt(0);
332363
return children;
333364
}
@@ -341,8 +372,8 @@ class EmptyLayoutElement extends LayoutElement {
341372
}
342373

343374
LayoutElement parseLayoutElement(
344-
dom.Element element,
345-
List<StyledElement> children,
375+
dom.Element element,
376+
List<StyledElement> children,
346377
) {
347378
switch (element.localName) {
348379
case "details":
@@ -353,8 +384,7 @@ LayoutElement parseLayoutElement(
353384
node: element,
354385
name: element.localName!,
355386
children: children,
356-
elementList: element.children
357-
);
387+
elementList: element.children);
358388
case "table":
359389
return TableLayoutElement(
360390
name: element.localName!,
@@ -376,9 +406,6 @@ LayoutElement parseLayoutElement(
376406
);
377407
default:
378408
return TableLayoutElement(
379-
children: children,
380-
name: "[[No Name]]",
381-
node: element
382-
);
409+
children: children, name: "[[No Name]]", node: element);
383410
}
384411
}

lib/src/styled_element.dart

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,19 @@ StyledElement parseStyledElement(
101101
//TODO(Sub6Resources) this is a workaround for collapsing margins. Remove.
102102
if (element.parent!.localName == "blockquote") {
103103
styledElement.style = Style(
104-
margin: const EdgeInsets.only(left: 40.0, right: 40.0, bottom: 14.0),
104+
margin: Margins.only(left: 40.0, right: 40.0, bottom: 14.0),
105105
display: Display.BLOCK,
106106
);
107107
} else {
108108
styledElement.style = Style(
109-
margin: const EdgeInsets.symmetric(horizontal: 40.0, vertical: 14.0),
109+
margin: Margins.symmetric(horizontal: 40.0, vertical: 14.0),
110110
display: Display.BLOCK,
111111
);
112112
}
113113
break;
114114
case "body":
115115
styledElement.style = Style(
116-
margin: EdgeInsets.all(8.0),
116+
margin: Margins.all(8.0),
117117
display: Display.BLOCK,
118118
);
119119
break;
@@ -133,7 +133,7 @@ StyledElement parseStyledElement(
133133
break;
134134
case "dd":
135135
styledElement.style = Style(
136-
margin: EdgeInsets.only(left: 40.0),
136+
margin: Margins.only(left: 40.0),
137137
display: Display.BLOCK,
138138
);
139139
break;
@@ -147,13 +147,13 @@ StyledElement parseStyledElement(
147147
continue italics;
148148
case "div":
149149
styledElement.style = Style(
150-
margin: EdgeInsets.all(0),
150+
margin: Margins.all(0),
151151
display: Display.BLOCK,
152152
);
153153
break;
154154
case "dl":
155155
styledElement.style = Style(
156-
margin: EdgeInsets.symmetric(vertical: 14.0),
156+
margin: Margins.symmetric(vertical: 14.0),
157157
display: Display.BLOCK,
158158
);
159159
break;
@@ -171,7 +171,7 @@ StyledElement parseStyledElement(
171171
break;
172172
case "figure":
173173
styledElement.style = Style(
174-
margin: EdgeInsets.symmetric(vertical: 14.0, horizontal: 40.0),
174+
margin: Margins.symmetric(vertical: 14.0, horizontal: 40.0),
175175
display: Display.BLOCK,
176176
);
177177
break;
@@ -195,47 +195,47 @@ StyledElement parseStyledElement(
195195
styledElement.style = Style(
196196
fontSize: FontSize.xxLarge,
197197
fontWeight: FontWeight.bold,
198-
margin: EdgeInsets.symmetric(vertical: 18.67),
198+
margin: Margins.symmetric(vertical: 18.67),
199199
display: Display.BLOCK,
200200
);
201201
break;
202202
case "h2":
203203
styledElement.style = Style(
204204
fontSize: FontSize.xLarge,
205205
fontWeight: FontWeight.bold,
206-
margin: EdgeInsets.symmetric(vertical: 17.5),
206+
margin: Margins.symmetric(vertical: 17.5),
207207
display: Display.BLOCK,
208208
);
209209
break;
210210
case "h3":
211211
styledElement.style = Style(
212212
fontSize: FontSize(16.38),
213213
fontWeight: FontWeight.bold,
214-
margin: EdgeInsets.symmetric(vertical: 16.5),
214+
margin: Margins.symmetric(vertical: 16.5),
215215
display: Display.BLOCK,
216216
);
217217
break;
218218
case "h4":
219219
styledElement.style = Style(
220220
fontSize: FontSize.medium,
221221
fontWeight: FontWeight.bold,
222-
margin: EdgeInsets.symmetric(vertical: 18.5),
222+
margin: Margins.symmetric(vertical: 18.5),
223223
display: Display.BLOCK,
224224
);
225225
break;
226226
case "h5":
227227
styledElement.style = Style(
228228
fontSize: FontSize(11.62),
229229
fontWeight: FontWeight.bold,
230-
margin: EdgeInsets.symmetric(vertical: 19.25),
230+
margin: Margins.symmetric(vertical: 19.25),
231231
display: Display.BLOCK,
232232
);
233233
break;
234234
case "h6":
235235
styledElement.style = Style(
236236
fontSize: FontSize(9.38),
237237
fontWeight: FontWeight.bold,
238-
margin: EdgeInsets.symmetric(vertical: 22),
238+
margin: Margins.symmetric(vertical: 22),
239239
display: Display.BLOCK,
240240
);
241241
break;
@@ -246,7 +246,7 @@ StyledElement parseStyledElement(
246246
break;
247247
case "hr":
248248
styledElement.style = Style(
249-
margin: EdgeInsets.symmetric(vertical: 7.0),
249+
margin: Margins.symmetric(vertical: 7.0),
250250
width: double.infinity,
251251
height: 1,
252252
backgroundColor: Colors.black,
@@ -317,14 +317,14 @@ StyledElement parseStyledElement(
317317
break;
318318
case "p":
319319
styledElement.style = Style(
320-
margin: EdgeInsets.symmetric(vertical: 14.0),
320+
margin: Margins.symmetric(vertical: 14.0),
321321
display: Display.BLOCK,
322322
);
323323
break;
324324
case "pre":
325325
styledElement.style = Style(
326326
fontFamily: 'monospace',
327-
margin: EdgeInsets.symmetric(vertical: 14.0),
327+
margin: Margins.symmetric(vertical: 14.0),
328328
whiteSpace: WhiteSpace.PRE,
329329
display: Display.BLOCK,
330330
);

0 commit comments

Comments
 (0)