Skip to content

Commit cf019bc

Browse files
Merge pull request #1306 from Sub6Resources/fix/intrinsics-and-table
fix: Better support for intrinsics, table, vertical-align, and display
2 parents 79ec194 + 90550dd commit cf019bc

File tree

10 files changed

+442
-108
lines changed

10 files changed

+442
-108
lines changed

lib/src/builtins/image_builtin.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ class ImageBuiltIn extends HtmlExtension {
9393
}
9494

9595
return WidgetSpan(
96+
alignment: context.style!.verticalAlign
97+
.toPlaceholderAlignment(context.style!.display),
98+
baseline: TextBaseline.alphabetic,
9699
child: CssBoxWidget(
97100
style: imageStyle,
98101
childIsReplaced: true,

lib/src/builtins/interactive_element_builtin.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,20 @@ class InteractiveElementBuiltIn extends HtmlExtension {
6161
children: childSpan.children
6262
?.map((e) => _processInteractableChild(context, e))
6363
.toList(),
64+
recognizer: TapGestureRecognizer()..onTap = onTap,
6465
style: childSpan.style,
6566
semanticsLabel: childSpan.semanticsLabel,
66-
recognizer: TapGestureRecognizer()..onTap = onTap,
67+
locale: childSpan.locale,
68+
mouseCursor: childSpan.mouseCursor,
69+
onEnter: childSpan.onEnter,
70+
onExit: childSpan.onExit,
71+
spellOut: childSpan.spellOut,
6772
);
6873
} else {
6974
return WidgetSpan(
75+
alignment: context.style!.verticalAlign
76+
.toPlaceholderAlignment(context.style!.display),
77+
baseline: TextBaseline.alphabetic,
7078
child: MultipleTapGestureDetector(
7179
onTap: onTap,
7280
child: GestureDetector(

lib/src/builtins/styled_element_builtin.dart

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -427,27 +427,27 @@ class StyledElementBuiltIn extends HtmlExtension {
427427

428428
@override
429429
InlineSpan build(ExtensionContext context) {
430-
if (context.styledElement!.style.display == Display.listItem ||
431-
((context.styledElement!.style.display == Display.block ||
432-
context.styledElement!.style.display == Display.inlineBlock) &&
430+
final style = context.styledElement!.style;
431+
final display = style.display ?? Display.inline;
432+
if (display.displayListItem ||
433+
((display.isBlock || display == Display.inlineBlock) &&
433434
(context.styledElement!.children.isNotEmpty ||
434435
context.elementName == "hr"))) {
435436
return WidgetSpan(
436-
alignment: PlaceholderAlignment.baseline,
437+
alignment: style.verticalAlign.toPlaceholderAlignment(display),
437438
baseline: TextBaseline.alphabetic,
438439
child: CssBoxWidget.withInlineSpanChildren(
439440
key: AnchorKey.of(context.parser.key, context.styledElement),
440-
style: context.styledElement!.style,
441+
style: context.style!,
441442
shrinkWrap: context.parser.shrinkWrap,
442-
childIsReplaced: ["iframe", "img", "video", "audio"]
443-
.contains(context.styledElement!.name),
443+
childIsReplaced:
444+
["iframe", "img", "video", "audio"].contains(context.elementName),
444445
children: context.builtChildrenMap!.entries
445446
.expandIndexed((i, child) => [
446447
child.value,
447448
if (context.parser.shrinkWrap &&
448449
i != context.styledElement!.children.length - 1 &&
449-
(child.key.style.display == Display.block ||
450-
child.key.style.display == Display.listItem) &&
450+
(child.key.style.display?.isBlock ?? false) &&
451451
child.key.element?.localName != "html" &&
452452
child.key.element?.localName != "body")
453453
const TextSpan(text: "\n", style: TextStyle(fontSize: 0)),
@@ -463,7 +463,7 @@ class StyledElementBuiltIn extends HtmlExtension {
463463
.expandIndexed((index, child) => [
464464
child.value,
465465
if (context.parser.shrinkWrap &&
466-
child.key.style.display == Display.block &&
466+
(child.key.style.display?.isBlock ?? false) &&
467467
index != context.styledElement!.children.length - 1 &&
468468
child.key.element?.parent?.localName != "th" &&
469469
child.key.element?.parent?.localName != "td" &&

lib/src/css_box_widget.dart

Lines changed: 85 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,7 @@ class CssBoxWidget extends StatelessWidget {
159159
/// width available to it or if it should just let its inner content
160160
/// determine the content-box's width.
161161
bool _shouldExpandToFillBlock() {
162-
return (style.display == Display.block ||
163-
style.display == Display.listItem) &&
164-
!childIsReplaced &&
165-
!shrinkWrap;
162+
return (style.display?.isBlock ?? false) && !childIsReplaced && !shrinkWrap;
166163
}
167164

168165
TextDirection _checkTextDirection(
@@ -424,42 +421,62 @@ class RenderCSSBox extends RenderBox
424421
}
425422
}
426423

427-
static double getIntrinsicDimension(RenderBox? firstChild,
428-
double Function(RenderBox child) mainChildSizeGetter) {
424+
static double getIntrinsicDimension(
425+
RenderBox? firstChild,
426+
double Function(RenderBox child) mainChildSizeGetter,
427+
double marginSpaceNeeded) {
429428
double extent = 0.0;
430429
RenderBox? child = firstChild;
431430
while (child != null) {
432431
final CSSBoxParentData childParentData =
433432
child.parentData! as CSSBoxParentData;
434-
extent = math.max(extent, mainChildSizeGetter(child));
433+
try {
434+
extent = math.max(extent, mainChildSizeGetter(child));
435+
} catch (_) {
436+
// See https://github.com/flutter/flutter/issues/65895
437+
debugPrint(
438+
"Due to Flutter layout restrictions (see https://github.com/flutter/flutter/issues/65895), contents set to `vertical-align: baseline` within an intrinsically-sized layout may not display as expected. If content is cut off or displaying incorrectly, please try setting vertical-align to 'bottom' on the problematic elements");
439+
}
435440
assert(child.parentData == childParentData);
436441
child = childParentData.nextSibling;
437442
}
438-
return extent;
443+
return extent + marginSpaceNeeded;
439444
}
440445

441446
@override
442447
double computeMinIntrinsicWidth(double height) {
443448
return getIntrinsicDimension(
444-
firstChild, (RenderBox child) => child.getMinIntrinsicWidth(height));
449+
firstChild,
450+
(RenderBox child) => child.getMinIntrinsicWidth(height),
451+
_calculateIntrinsicMargins().horizontal,
452+
);
445453
}
446454

447455
@override
448456
double computeMaxIntrinsicWidth(double height) {
449457
return getIntrinsicDimension(
450-
firstChild, (RenderBox child) => child.getMaxIntrinsicWidth(height));
458+
firstChild,
459+
(RenderBox child) => child.getMaxIntrinsicWidth(height),
460+
_calculateIntrinsicMargins().horizontal,
461+
);
451462
}
452463

453464
@override
454465
double computeMinIntrinsicHeight(double width) {
455466
return getIntrinsicDimension(
456-
firstChild, (RenderBox child) => child.getMinIntrinsicHeight(width));
467+
firstChild,
468+
(RenderBox child) => child.getMinIntrinsicHeight(width),
469+
_calculateIntrinsicMargins().vertical,
470+
);
457471
}
458472

459473
@override
460474
double computeMaxIntrinsicHeight(double width) {
461475
return getIntrinsicDimension(
462-
firstChild, (RenderBox child) => child.getMaxIntrinsicHeight(width));
476+
firstChild,
477+
(RenderBox child) => child.getMaxIntrinsicHeight(width),
478+
_calculateIntrinsicMargins().vertical,
479+
);
463480
}
464481

465482
@override
@@ -521,31 +538,20 @@ class RenderCSSBox extends RenderBox
521538

522539
//Calculate Width and Height of CSS Box
523540
height = childSize.height;
524-
switch (display) {
525-
case Display.block:
526-
width = (shrinkWrap || childIsReplaced)
527-
? childSize.width + horizontalMargins
528-
: containingBlockSize.width;
529-
height = childSize.height + verticalMargins;
530-
break;
531-
case Display.inline:
532-
width = childSize.width + horizontalMargins;
533-
height = childSize.height;
534-
break;
535-
case Display.inlineBlock:
536-
width = childSize.width + horizontalMargins;
537-
height = childSize.height + verticalMargins;
538-
break;
539-
case Display.listItem:
540-
width = shrinkWrap
541-
? childSize.width + horizontalMargins
542-
: containingBlockSize.width;
543-
height = childSize.height + verticalMargins;
544-
break;
545-
case Display.none:
546-
width = 0;
547-
height = 0;
548-
break;
541+
if (display.displayBox == DisplayBox.none) {
542+
width = 0;
543+
height = 0;
544+
} else if (display == Display.inlineBlock) {
545+
width = childSize.width + horizontalMargins;
546+
height = childSize.height + verticalMargins;
547+
} else if (display.isBlock) {
548+
width = (shrinkWrap || childIsReplaced)
549+
? childSize.width + horizontalMargins
550+
: containingBlockSize.width;
551+
height = childSize.height + verticalMargins;
552+
} else {
553+
width = childSize.width + horizontalMargins;
554+
height = childSize.height;
549555
}
550556

551557
return _Sizes(constraints.constrain(Size(width, height)), childSize);
@@ -575,26 +581,14 @@ class RenderCSSBox extends RenderBox
575581

576582
double leftOffset = 0;
577583
double topOffset = 0;
578-
switch (display) {
579-
case Display.block:
580-
leftOffset = leftMargin;
581-
topOffset = topMargin;
582-
break;
583-
case Display.inline:
584-
leftOffset = leftMargin;
585-
break;
586-
case Display.inlineBlock:
587-
leftOffset = leftMargin;
588-
topOffset = topMargin;
589-
break;
590-
case Display.listItem:
591-
leftOffset = leftMargin;
592-
topOffset = topMargin;
593-
break;
594-
case Display.none:
595-
//No offset
596-
break;
584+
585+
if (display.isBlock || display == Display.inlineBlock) {
586+
leftOffset = leftMargin;
587+
topOffset = topMargin;
588+
} else if (display.displayOutside == DisplayOutside.inline) {
589+
leftOffset = leftMargin;
597590
}
591+
598592
childParentData.offset = Offset(leftOffset, topOffset);
599593
assert(child.parentData == childParentData);
600594

@@ -628,7 +622,7 @@ class RenderCSSBox extends RenderBox
628622

629623
Margins _calculateUsedMargins(Size childSize, Size containingBlockSize) {
630624
//We assume that margins have already been preprocessed
631-
// (i.e. they are non-null and either px units or auto.
625+
// (i.e. they are non-null and either px units or auto).
632626
assert(margins.left != null && margins.right != null);
633627
assert(margins.left!.unit == Unit.px || margins.left!.unit == Unit.auto);
634628
assert(margins.right!.unit == Unit.px || margins.right!.unit == Unit.auto);
@@ -737,6 +731,40 @@ class RenderCSSBox extends RenderBox
737731
);
738732
}
739733

734+
Margins _calculateIntrinsicMargins() {
735+
//We assume that margins have already been preprocessed
736+
// (i.e. they are non-null and either px units or auto).
737+
assert(margins.left != null && margins.right != null);
738+
assert(margins.left!.unit == Unit.px || margins.left!.unit == Unit.auto);
739+
assert(margins.right!.unit == Unit.px || margins.right!.unit == Unit.auto);
740+
741+
Margin marginLeft = margins.left!;
742+
Margin marginRight = margins.right!;
743+
744+
bool marginLeftIsAuto = marginLeft.unit == Unit.auto;
745+
bool marginRightIsAuto = marginRight.unit == Unit.auto;
746+
747+
if (display.isBlock) {
748+
if (marginLeftIsAuto) {
749+
marginLeft = Margin(0);
750+
}
751+
752+
if (marginRightIsAuto) {
753+
marginRight = Margin(0);
754+
}
755+
} else {
756+
marginLeft = Margin(0);
757+
marginRight = Margin(0);
758+
}
759+
760+
return Margins(
761+
left: marginLeft,
762+
right: marginRight,
763+
top: margins.top,
764+
bottom: margins.bottom,
765+
);
766+
}
767+
740768
@override
741769
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
742770
return defaultHitTestChildren(result, position: position);

lib/src/processing/margins.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ class MarginProcessing {
3232
//Collapsing should be depth-first.
3333
tree.children.forEach(_collapseMargins);
3434

35-
//The root boxes do not collapse.
36-
if (tree.name == '[Tree Root]' || tree.name == 'html') {
35+
//The root boxes and table/ruby elements do not collapse.
36+
if (tree.name == '[Tree Root]' ||
37+
tree.name == 'html' ||
38+
tree.style.display?.displayInternal != null) {
3739
return tree;
3840
}
3941

@@ -67,7 +69,8 @@ class MarginProcessing {
6769

6870
// Handle case (3) from above.
6971
// Bottom margins cannot collapse if the element has padding
70-
if ((tree.style.padding?.bottom ?? tree.style.padding?.blockEnd ?? 0) ==
72+
if ((tree.style.padding?.bottom?.value ??
73+
tree.style.padding?.blockEnd?.value) ==
7174
0) {
7275
final parentBottom = tree.style.margin?.bottom?.value ??
7376
tree.style.margin?.blockEnd?.value ??

lib/src/style.dart

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter_html/flutter_html.dart';
55
import 'package:flutter_html/src/css_parser.dart';
66

77
//Export Style value-unit APIs
8+
export 'package:flutter_html/src/style/display.dart';
89
export 'package:flutter_html/src/style/margin.dart';
910
export 'package:flutter_html/src/style/padding.dart';
1011
export 'package:flutter_html/src/style/length.dart';
@@ -49,7 +50,7 @@ class Style {
4950
/// CSS attribute "`display`"
5051
///
5152
/// Inherited: no,
52-
/// Default: unspecified,
53+
/// Default: inline,
5354
Display? display;
5455

5556
/// CSS attribute "`font-family`"
@@ -271,8 +272,7 @@ class Style {
271272
this.textOverflow,
272273
this.textTransform = TextTransform.none,
273274
}) {
274-
if (alignment == null &&
275-
(display == Display.block || display == Display.listItem)) {
275+
if (alignment == null && (display?.isBlock ?? false)) {
276276
alignment = Alignment.centerLeft;
277277
}
278278
}
@@ -591,14 +591,6 @@ extension MergeBorders on Border? {
591591
}
592592
}
593593

594-
enum Display {
595-
block,
596-
inline,
597-
inlineBlock,
598-
listItem,
599-
none,
600-
}
601-
602594
enum ListStyleType {
603595
arabicIndic('arabic-indic'),
604596
armenian('armenian'),
@@ -692,7 +684,34 @@ enum VerticalAlign {
692684
sup,
693685
top,
694686
bottom,
695-
middle,
687+
middle;
688+
689+
/// Converts this [VerticalAlign] to a [PlaceholderAlignment] given the
690+
/// [Display] type of the current context
691+
PlaceholderAlignment toPlaceholderAlignment(Display? display) {
692+
// vertical-align only applies to inline context elements.
693+
// If we aren't in such a context, use the default 'bottom' alignment.
694+
// Also note that the default display, if it is not set, is inline, so we
695+
// treat null `display` values as if they were inline by default.
696+
if (display != Display.inline &&
697+
display != Display.inlineBlock &&
698+
display != null) {
699+
return PlaceholderAlignment.bottom;
700+
}
701+
702+
switch (this) {
703+
case VerticalAlign.baseline:
704+
case VerticalAlign.sub:
705+
case VerticalAlign.sup:
706+
return PlaceholderAlignment.baseline;
707+
case VerticalAlign.top:
708+
return PlaceholderAlignment.top;
709+
case VerticalAlign.bottom:
710+
return PlaceholderAlignment.bottom;
711+
case VerticalAlign.middle:
712+
return PlaceholderAlignment.middle;
713+
}
714+
}
696715
}
697716

698717
enum WhiteSpace {

0 commit comments

Comments
 (0)