8000 fix: Better support for intrinsics, table, vertical-align, and display by Sub6Resources · Pull Request #1306 · Sub6Resources/flutter_html · GitHub
[go: up one dir, main page]

Skip to content

fix: Better support for intrinsics, table, vertical-align, and display #1306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/src/builtins/image_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ class ImageBuiltIn extends HtmlExtension {
}

return WidgetSpan(
alignment: context.style!.verticalAlign
.toPlaceholderAlignment(context.style!.display),
baseline: TextBaseline.alphabetic,
child: CssBoxWidget(
style: imageStyle,
childIsReplaced: true,
Expand Down
10 changes: 9 additions & 1 deletion lib/src/builtins/interactive_element_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,20 @@ class InteractiveElementBuiltIn extends HtmlExtension {
children: childSpan.children
?.map((e) => _processInteractableChild(context, e))
.toList(),
recognizer: TapGestureRecognizer()..onTap = onTap,
style: childSpan.style,
semanticsLabel: childSpan.semanticsLabel,
recognizer: TapGestureRecognizer()..onTap = onTap,
locale: childSpan.locale,
mouseCursor: childSpan.mouseCursor,
onEnter: childSpan.onEnter,
onExit: childSpan.onExit,
spellOut: childSpan.spellOut,
);
} else {
return WidgetSpan(
alignment: context.style!.verticalAlign
.toPlaceholderAlignment(context.style!.display),
baseline: TextBaseline.alphabetic,
child: MultipleTapGestureDetector(
onTap: onTap,
child: GestureDetector(
Expand Down
20 changes: 10 additions & 10 deletions lib/src/builtins/styled_element_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -427,27 +427,27 @@

@override
InlineSpan build(ExtensionContext context) {
if (context.styledElement!.style.display == Display.listItem ||
((context.styledElement!.style.display == Display.block ||
context.styledElement!.style.display == Display.inlineBlock) &&
final style = context.styledElement!.style;
final display = style.display ?? Display.inline;
if (display.displayListItem ||
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove this

((display.isBlock || display == Display.inlineBlock) &&
(context.styledElement!.children.isNotEmpty ||
context.elementName == "hr"))) {
return WidgetSpan(
alignment: PlaceholderAlignment.baseline,
alignment: style.verticalAlign.toPlaceholderAlignment(display),
baseline: TextBaseline.alphabetic,
child: CssBoxWidget.withInlineSpanChildren(
key: AnchorKey.of(context.parser.key, context.styledElement),
style: context.styledElement!.style,
style: context.style!,
shrinkWrap: context.parser.shrinkWrap,
childIsReplaced: ["iframe", "img", "video", "audio"]
.contains(context.styledElement!.name),
childIsReplaced:
["iframe", "img", "video", "audio"].contains(context.elementName),
children: context.builtChildrenMap!.entries
.expandIndexed((i, child) => [
child.value,
if (context.parser.shrinkWrap &&
i != context.styledElement!.children.length - 1 &&
(child.key.style.display == Display.block ||
child.key.style.display == Display.listItem) &&
(child.key.style.display?.isBlock ?? false) &&

Check warning on line 450 in lib/src/bui 8000 ltins/styled_element_builtin.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/builtins/styled_element_builtin.dart#L450

Added line #L450 was not covered by tests
child.key.element?.localName != "html" &&
child.key.element?.localName != "body")
const TextSpan(text: "\n", style: TextStyle(fontSize: 0)),
Expand All @@ -463,7 +463,7 @@
.expandIndexed((index, child) => [
child.value,
if (context.parser.shrinkWrap &&
child.key.style.display == Display.block &&
(child.key.style.display?.isBlock ?? false) &&

Check warning on line 466 in lib/src/builtins/styled_element_builtin.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/builtins/styled_element_builtin.dart#L466

Added line #L466 was not covered by tests
index != context.styledElement!.children.length - 1 &&
child.key.element?.parent?.localName != "th" &&
child.key.element?.parent?.localName != "td" &&
Expand Down
142 changes: 85 additions & 57 deletions lib/src/css_box_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,7 @@
/// width available to it or if it should just let its inner content
/// determine the content-box's width.
bool _shouldExpandToFillBlock() {
return (style.display == Display.block ||
style.display == Display.listItem) &&
!childIsReplaced &&
!shrinkWrap;
return (style.display?.isBlock ?? false) && !childIsReplaced && !shrinkWrap;
}

TextDirection _checkTextDirection(
Expand Down Expand Up @@ -424,42 +421,62 @@
}
}

static double getIntrinsicDimension(RenderBox? firstChild,
double Function(RenderBox child) mainChildSizeGetter) {
static double getIntrinsicDimension(

Check warning on line 424 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L424

Added line #L424 was not covered by tests
RenderBox? firstChild,
double Function(RenderBox child) mainChildSizeGetter,
double marginSpaceNeeded) {
double extent = 0.0;
RenderBox? child = firstChild;
while (child != null) {
final CSSBoxParentData childParentData =
child.parentData! as CSSBoxParentData;
extent = math.max(extent, mainChildSizeGetter(child));
try {
extent = math.max(extent, mainChildSizeGetter(child));

Check warning on line 434 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L434

Added line #L434 was not covered by tests
} catch (_) {
// See https://github.com/flutter/flutter/issues/65895
debugPrint(

Check warning on line 437 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L437

Added line #L437 was not covered by tests
"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");
}
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
return extent;
return extent + marginSpaceNeeded;

Check warning on line 443 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L443

Added line #L443 was not covered by tests
}

@override
double computeMinIntrinsicWidth(double height) {
return getIntrinsicDimension(
firstChild, (RenderBox child) => child.getMinIntrinsicWidth(height));
firstChild,
(RenderBox child) => child.getMinIntrinsicWidth(height),
_calculateIntrinsicMargins().horizontal,

Check warning on line 451 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L449-L451

Added lines #L449 - L451 were not covered by tests
);
}

@override
double computeMaxIntrinsicWidth(double height) {
return getIntrinsicDimension(
firstChild, (RenderBox child) => child.getMaxIntrinsicWidth(height));
firstChild,
(RenderBox child) => child.getMaxIntrinsicWidth(height),
_calculateIntrinsicMargins().horizontal,

Check warning on line 460 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L458-L460

Added lines #L458 - L460 were not covered by tests
);
}

@override
double computeMinIntrinsicHeight(double width) {
return getIntrinsicDimension(
firstChild, (RenderBox child) => child.getMinIntrinsicHeight(width));
firstChild,
(RenderBox child) => child.getMinIntrinsicHeight(width),
_calculateIntrinsicMargins().vertical,

Check warning on line 469 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L467-L469

Added lines #L467 - L469 were not covered by tests
);
}

@override
double computeMaxIntrinsicHeight(double width) {
return getIntrinsicDimension(
firstChild, (RenderBox child) => child.getMaxIntrinsicHeight(width));
firstChild,
(RenderBox child) => child.getMaxIntrinsicHeight(width),
_calculateIntrinsicMargins().vertical,

Check warning on line 478 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L476-L478

Added lines #L476 - L478 were not covered by tests
);
}

@override
Expand Down Expand Up @@ -521,31 +538,20 @@

//Calculate Width and Height of CSS Box
height = childSize.height;
switch (display) {
case Display.block:
width = (shrinkWrap || childIsReplaced)
? childSize.width + horizontalMargins
: containingBlockSize.width;
height = childSize.height + verticalMargins;
break;
case Display.inline:
width = childSize.width + horizontalMargins;
height = childSize.height;
break;
case Display.inlineBlock:
width = childSize.width + horizontalMargins;
height = childSize.height + verticalMargins;
break;
case Display.listItem:
width = shrinkWrap
? childSize.width + horizontalMargins
: containingBlockSize.width;
height = childSize.height + verticalMargins;
break;
case Display.none:
width = 0;
height = 0;
break;
if (display.displayBox == DisplayBox.none) {
width = 0;
height = 0;
} else if (display == Display.inlineBlock) {
width = childSize.width + horizontalMargins;
height = childSize.height + verticalMargins;

Check warning on line 546 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L545-L546

Added lines #L545 - L546 were not covered by tests
} else if (display.isBlock) {
width = (shrinkWrap || childIsReplaced)
? childSize.width + horizontalMargins

Check warning on line 549 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L549

Added line #L549 was not covered by tests
: containingBlockSize.width;
height = childSize.height + verticalMargins;
} else {
width = childSize.width + horizontalMargins;
height = childSize.height;
}

return _Sizes(constraints.constrain(Size(width, height)), childSize);
Expand Down Expand Up @@ -575,26 +581,14 @@

double leftOffset = 0;
double topOffset = 0;
switch (display) {
case Display.block:
leftOffset = leftMargin;
topOffset = topMargin;
break;
case Display.inline:
leftOffset = leftMargin;
break;
case Display.inlineBlock:
leftOffset = leftMargin;
topOffset = topMargin;
break;
case Display.listItem:
leftOffset = leftMargin;
topOffset = topMargin;
break;
case Display.none:
//No offset
break;

if (display.isBlock || display == Display.inlineBlock) {
leftOffset = leftMargin;
topOffset = topMargin;
} else if (display.displayOutside == DisplayOutside.inline) {
leftOffset = leftMargin;
}

childParentData.offset = Offset(leftOffset, topOffset);
assert(child.parentData == childParentData);

Expand Down Expand Up @@ -628,7 +622,7 @@

Margins _calculateUsedMargins(Size childSize, Size containingBlockSize) {
//We assume that margins have already been preprocessed
// (i.e. they are non-null and either px units or auto.
// (i.e. they are non-null and either px units or auto).
assert(margins.left != null && margins.right != null);
assert(margins.left!.unit == Unit.px || margins.left!.unit == Unit.auto);
assert(margins.right!.unit == Unit.px || margins.right!.unit == Unit.auto);
Expand Down Expand Up @@ -737,6 +731,40 @@
);
}

Margins _calculateIntrinsicMargins() {

Check warning on line 734 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L734

Added line #L734 was not covered by tests
//We assume that margins have already been preprocessed
// (i.e. they are non-null and either px units or auto).
assert(margins.left != null && margins.right != null);
assert(margins.left!.unit == Unit.px || margins.left!.unit == Unit.auto);
assert(margins.right!.unit == Unit.px || margins.right!.unit == Unit.auto);

Check warning on line 739 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L737-L739

Added lines #L737 - L739 were not covered by tests

Margin marginLeft = margins.left!;
Margin marginRight = margins.right!;

Check warning on line 742 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L741-L742

Added lines #L741 - L742 were not covered by tests

bool marginLeftIsAuto = marginLeft.unit == Unit.auto;
bool marginRightIsAuto = marginRight.unit == Unit.auto;

Check warning on line 745 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L744-L745

Added lines #L744 - L745 were not covered by tests

if (display.isBlock) {

Check warning on line 747 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L747

Added line #L747 was not covered by tests
if (marginLeftIsAuto) {
marginLeft = Margin(0);

Check warning on line 749 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L749

Added line #L749 was not covered by tests
}

if (marginRightIsAuto) {
marginRight = Margin(0);

Check warning on line 753 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L753

Added line #L753 was not covered by tests
}
} else {
marginLeft = Margin(0);
marginRight = Margin(0);

Check warning on line 757 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L756-L757

Added lines #L756 - L757 were not covered by tests
}

return Margins(

Check warning on line 760 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L760

Added line #L760 was not covered by tests
left: marginLeft,
right: marginRight,
top: margins.top,
bottom: margins.bottom,

Check warning on line 764 in lib/src/css_box_widget.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/css_box_widget.dart#L763-L764

Added lines #L763 - L764 were not covered by tests
);
}

@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return defaultHitTestChildren(result, position: position);
Expand Down
9 changes: 6 additions & 3 deletions lib/src/processing/margins.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ class MarginProcessing {
//Collapsing should be depth-first.
tree.children.forEach(_collapseMargins);

//The root boxes do not collapse.
if (tree.name == '[Tree Root]' || tree.name == 'html') {
//The root boxes and table/ruby elements do not collapse.
if (tree.name == '[Tree Root]' ||
tree.name == 'html' ||
tree.style.display?.displayInternal != null) {
return tree;
}

Expand Down Expand Up @@ -67,7 +69,8 @@ class MarginProcessing {

// Handle case (3) from above.
// Bottom margins cannot collapse if the element has padding
if ((tree.style.padding?.bottom ?? tree.style.padding?.blockEnd ?? 0) ==
if ((tree.style.padding?.bottom?.value ??
tree.style.padding?.blockEnd?.value) ==
0) {
final parentBottom = tree.style.margin?.bottom?.value ??
tree.style.margin?.blockEnd?.value ??
Expand Down
43 changes: 31 additions & 12 deletions lib/src/style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'package:flutter_html/src/css_parser.dart';

//Export Style value-unit APIs
export 'package:flutter_html/src/style/display.dart';
export 'package:flutter_html/src/style/margin.dart';
export 'package:flutter_html/src/style/padding.dart';
export 'package:flutter_html/src/style/length.dart';
Expand Down Expand Up @@ -49,7 +50,7 @@
/// CSS attribute "`display`"
///
/// Inherited: no,
/// Default: unspecified,
/// Default: inline,
Display? display;

/// CSS attribute "`font-family`"
Expand Down Expand Up @@ -271,8 +272,7 @@
this.textOverflow,
this.textTransform = TextTransform.none,
}) {
if (alignment == null &&
(display == Display.block || display == Display.listItem)) {
if (alignment == null && (display?.isBlock ?? false)) {
alignment = Alignment.centerLeft;
}
}
Expand Down Expand Up @@ -591,14 +591,6 @@
}
}

enum Display {
block,
inline,
inlineBlock,
listItem,
none,
}

enum ListStyleType {
arabicIndic('arabic-indic'),
armenian('armenian'),
Expand Down Expand Up @@ -692,7 +684,34 @@
sup,
top,
bottom,
middle,
middle;

/// Converts this [VerticalAlign] to a [PlaceholderAlignment] given the
/// [Display] type of the current context
PlaceholderAlignment toPlaceholderAlignment(Display? display) {
// vertical-align only applies to inline context elements.
// If we aren't in such a context, use the default 'bottom' alignment.
// Also note that the default display, if it is not set, is inline, so we
// treat null `display` values as if they were inline by default.
if (display != Display.inline &&
display != Display.inlineBlock &&
display != null) {
return PlaceholderAlignment.bottom;
}

switch (this) {
case VerticalAlign.baseline:
case VerticalAlign.sub:
case VerticalAlign.sup:

Check warning on line 705 in lib/src/style.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/style.dart#L704-L705

Added lines #L704 - L705 were not covered by tests
return PlaceholderAlignment.baseline;
case VerticalAlign.top:

Check warning on line 707 in lib/src/style.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/style.dart#L707

Added line #L707 was not covered by tests
return PlaceholderAlignment.top;
case VerticalAlign.bottom:

Check warning on line 709 in lib/src/style.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/style.dart#L709

Added line #L709 was not covered by tests
return PlaceholderAlignment.bottom;
case VerticalAlign.middle:

Check warning on line 711 in lib/src/style.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/style.dart#L711

Added line #L711 was not covered by tests
return PlaceholderAlignment.middle;
}
}
}

enum WhiteSpace {
Expand Down
Loading
0