From f229cae85f2d67e85fd24db34fa73012a482fcf0 Mon Sep 17 00:00:00 2001 From: Matthew Whitaker Date: Sat, 3 Jun 2023 08:14:03 -0700 Subject: [PATCH 1/3] feat: Expand Display support --- lib/src/style.dart | 41 +++++-- lib/src/style/display.dart | 239 +++++++++++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+), 12 deletions(-) create mode 100644 lib/src/style/display.dart diff --git a/lib/src/style.dart b/lib/src/style.dart index 004b32f4e7..d5d1e0b72d 100644 --- a/lib/src/style.dart +++ b/lib/src/style.dart @@ -5,6 +5,7 @@ import 'package:flutter_html/flutter_html.dart'; 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'; @@ -49,7 +50,7 @@ class Style { /// CSS attribute "`display`" /// /// Inherited: no, - /// Default: unspecified, + /// Default: inline, Display? display; /// CSS attribute "`font-family`" @@ -271,8 +272,7 @@ class Style { this.textOverflow, this.textTransform = TextTransform.none, }) { - if (alignment == null && - (display == Display.block || display == Display.listItem)) { + if (alignment == null && (display?.isBlock ?? false)) { alignment = Alignment.centerLeft; } } @@ -591,14 +591,6 @@ extension MergeBorders on Border? { } } -enum Display { - block, - inline, - inlineBlock, - listItem, - none, -} - enum ListStyleType { arabicIndic('arabic-indic'), armenian('armenian'), @@ -692,7 +684,32 @@ enum VerticalAlign { 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. + if(display != Display.inline && display != Display.inlineBlock) { + return PlaceholderAlignment.bottom; + } + + switch(this) { + case VerticalAlign.baseline: + case VerticalAlign.sub: + case VerticalAlign.sup: + return PlaceholderAlignment.baseline; + case VerticalAlign.top: + return PlaceholderAlignment.top; + case VerticalAlign.bottom: + return PlaceholderAlignment.bottom; + case VerticalAlign.middle: + return PlaceholderAlignment.middle; + } + + } } enum WhiteSpace { diff --git a/lib/src/style/display.dart b/lib/src/style/display.dart new file mode 100644 index 0000000000..77914d4c50 --- /dev/null +++ b/lib/src/style/display.dart @@ -0,0 +1,239 @@ +/// Equivalent to CSS `display` +/// +/// (https://www.w3.org/TR/css-display-3/#the-display-properties) +enum Display { + /// Equivalent to css `display: none;` + none ( + displayBox: DisplayBox.none, + ), + + /// Equivalent to css `display: contents;` + /// + /// Not supported by flutter_html + contents ( + displayBox: DisplayBox.contents, + ), + + /// Equivalent to css `display: block;` + block ( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.flow, + ), + + /// Equivalent to css `display: flow-root;` + /// + /// Not supported by flutter_html + flowRoot ( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.flowRoot, + ), + + /// Equivalent to css `display: inline;` + inline ( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.flow, + ), + + /// Equivalent to css `display: inline-block;` + inlineBlock ( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.flowRoot, + ), + + /// Equivalent to css `display: run-in;` + /// + /// Not supported by flutter_html + runIn ( + displayOutside: DisplayOutside.runIn, + displayInside: DisplayInside.flow, + ), + + /// Equivalent to css `display: list-item;` + listItem ( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.flow, + displayListItem: true, + ), + + /// Equivalent to css `display: inline list-item;` + inlineListItem ( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.flow, + displayListItem: true, + ), + + /// Equivalent to css `display: flex;` + /// + /// Not supported by flutter_html + flex ( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.flex, + ), + + /// Equivalent to css `display: inline-flex;` + /// + /// Not supported by flutter_html + inlineFlex ( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.flex, + ), + + /// Equivalent to css `display: grid;` + /// + /// Not supported by flutter_html + grid ( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.grid, + ), + + /// Equivalent to css `display: inline-grid;` + /// + /// Not supported by flutter_html + inlineGrid ( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.grid, + ), + + /// Equivalent to css `display: ruby;` + ruby ( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.ruby, + ), + + /// Equivalent to css `display: block ruby;` + blockRuby ( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.ruby, + ), + + /// Equivalent to css `display: table;` + table ( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.table, + ), + + /// Equivalent to css `display: inline-table;` + inlineTable ( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.table, + ), + + /// Equivalent to css `display: table-row-group;` + tableRowGroup ( + displayInternal: DisplayInternal.tableRowGroup, + ), + + /// Equivalent to css `display: table-header-group;` + tableHeaderGroup ( + displayInternal: DisplayInternal.tableHeaderGroup, + ), + + /// Equivalent to css `display: table-footer-group;` + tableFooterGroup ( + displayInternal: DisplayInternal.tableFooterGroup, + ), + + /// Equivalent to css `display: table-row;` + tableRow ( + displayInternal: DisplayInternal.tableRowGroup, + ), + + /// Equivalent to css `display: table-cell;` + tableCell ( + displayInternal: DisplayInternal.tableCell, + displayInside: DisplayInside.flowRoot, + ), + + /// Equivalent to css `display: table-column-group;` + tableColumnGroup ( + displayInternal: DisplayInternal.tableColumnGroup, + ), + + /// Equivalent to css `display: table-column;` + tableColumn ( + displayInternal: DisplayInternal.tableColumn, + ), + + /// Equivalent to css `display: table-caption;` + tableCaption ( + displayInternal: DisplayInternal.tableCaption, + displayInside: DisplayInside.flowRoot, + ), + + /// Equivalent to css `display: ruby-base;` + rubyBase ( + displayInternal: DisplayInternal.rubyBase, + displayInside: DisplayInside.flow, + ), + + /// Equivalent to css `display: ruby-text;` + rubyText ( + displayInternal: DisplayInternal.rubyText, + displayInside: DisplayInside.flow, + ), + + /// Equivalent to css `display: ruby-base-container;` + rubyBaseContainer ( + displayInternal: DisplayInternal.rubyBaseContainer, + ), + + /// Equivalent to css `display: ruby-text-container;` + rubyTextContainer ( + displayInternal: DisplayInternal.rubyTextContainer, + ); + + const Display({ + this.displayOutside, + this.displayInside, + this.displayListItem = false, + this.displayInternal, + this.displayBox, + }); + + final DisplayOutside? displayOutside; + final DisplayInside? displayInside; + final bool displayListItem; + final DisplayInternal? displayInternal; + final DisplayBox? displayBox; + + /// Evaluates to `true` if `displayOutside` is equal to + /// `DisplayOutside.block`. + bool get isBlock { + return displayOutside == DisplayOutside.block; + } +} + +enum DisplayOutside { + block, + inline, + runIn, // not supported +} + +enum DisplayInside { + flow, + flowRoot, + table, + flex, // not supported + grid, // not supported + ruby, +} + +enum DisplayInternal { + tableRowGroup, + tableHeaderGroup, + tableFooterGroup, + tableRow, + tableCell, + tableColumnGroup, + tableColumn, + tableCaption, + rubyBase, + rubyText, + rubyBaseContainer, + rubyTextContainer, +} + +enum DisplayBox { + contents, // not supported + none, +} \ No newline at end of file From f74c2a57fa6b98957c2e7808da571af991f5d471 Mon Sep 17 00:00:00 2001 From: Matthew Whitaker Date: Sat, 3 Jun 2023 08:15:27 -0700 Subject: [PATCH 2/3] fix: Fix many intrinsic issues and add better support for vertical-align throughout --- lib/src/builtins/image_builtin.dart | 2 ++ lib/src/builtins/interactive_element_builtin.dart | 2 ++ lib/src/builtins/styled_element_builtin.dart | 9 +++++---- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/src/builtins/image_builtin.dart b/lib/src/builtins/image_builtin.dart index 08bfd1b70d..e3e140ee13 100644 --- a/lib/src/builtins/image_builtin.dart +++ b/lib/src/builtins/image_builtin.dart @@ -93,6 +93,8 @@ class ImageBuiltIn extends HtmlExtension { } return WidgetSpan( + alignment: context.style!.verticalAlign.toPlaceholderAlignment(context.style!.display), + baseline: TextBaseline.alphabetic, child: CssBoxWidget( style: imageStyle, childIsReplaced: true, diff --git a/lib/src/builtins/interactive_element_builtin.dart b/lib/src/builtins/interactive_element_builtin.dart index e8486b0701..c22861a086 100644 --- a/lib/src/builtins/interactive_element_builtin.dart +++ b/lib/src/builtins/interactive_element_builtin.dart @@ -67,6 +67,8 @@ class InteractiveElementBuiltIn extends HtmlExtension { ); } else { return WidgetSpan( + alignment: context.style!.verticalAlign.toPlaceholderAlignment(context.style!.display), + baseline: TextBaseline.alphabetic, child: MultipleTapGestureDetector( onTap: onTap, child: GestureDetector( diff --git a/lib/src/builtins/styled_element_builtin.dart b/lib/src/builtins/styled_element_builtin.dart index 67e5b29e61..debb8e8daf 100644 --- a/lib/src/builtins/styled_element_builtin.dart +++ b/lib/src/builtins/styled_element_builtin.dart @@ -427,13 +427,14 @@ class StyledElementBuiltIn extends HtmlExtension { @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; + if (display == Display.listItem || + ((display == Display.block || 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), From 90550ddeab4eabf4f715d15e56c5a3da82c37373 Mon Sep 17 00:00:00 2001 From: Matthew Whitaker Date: Tue, 6 Jun 2023 19:26:21 -0700 Subject: [PATCH 3/3] Add fixes for some table and intrinsic sizing issues --- lib/src/builtins/image_builtin.dart | 3 +- .../builtins/interactive_element_builtin.dart | 10 +- lib/src/builtins/styled_element_builtin.dart | 17 +-- lib/src/css_box_widget.dart | 142 +++++++++++------- lib/src/processing/margins.dart | 9 +- lib/src/style.dart | 10 +- lib/src/style/display.dart | 60 ++++---- lib/src/style/margin.dart | 13 ++ .../lib/flutter_html_table.dart | 63 +++++--- test/style/css_parsing/margin_test.dart | 8 +- 10 files changed, 204 insertions(+), 131 deletions(-) diff --git a/lib/src/builtins/image_builtin.dart b/lib/src/builtins/image_builtin.dart index e3e140ee13..a94ee2a71a 100644 --- a/lib/src/builtins/image_builtin.dart +++ b/lib/src/builtins/image_builtin.dart @@ -93,7 +93,8 @@ class ImageBuiltIn extends HtmlExtension { } return WidgetSpan( - alignment: context.style!.verticalAlign.toPlaceholderAlignment(context.style!.display), + alignment: context.style!.verticalAlign + .toPlaceholderAlignment(context.style!.display), baseline: TextBaseline.alphabetic, child: CssBoxWidget( style: imageStyle, diff --git a/lib/src/builtins/interactive_element_builtin.dart b/lib/src/builtins/interactive_element_builtin.dart index c22861a086..f2537b9cd3 100644 --- a/lib/src/builtins/interactive_element_builtin.dart +++ b/lib/src/builtins/interactive_element_builtin.dart @@ -61,13 +61,19 @@ 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), + alignment: context.style!.verticalAlign + .toPlaceholderAlignment(context.style!.display), baseline: TextBaseline.alphabetic, child: MultipleTapGestureDetector( onTap: onTap, diff --git a/lib/src/builtins/styled_element_builtin.dart b/lib/src/builtins/styled_element_builtin.dart index debb8e8daf..a750c93187 100644 --- a/lib/src/builtins/styled_element_builtin.dart +++ b/lib/src/builtins/styled_element_builtin.dart @@ -428,9 +428,9 @@ class StyledElementBuiltIn extends HtmlExtension { @override InlineSpan build(ExtensionContext context) { final style = context.styledElement!.style; - final display = style.display; - if (display == Display.listItem || - ((display == Display.block || display == Display.inlineBlock) && + final display = style.display ?? Display.inline; + if (display.displayListItem || + ((display.isBlock || display == Display.inlineBlock) && (context.styledElement!.children.isNotEmpty || context.elementName == "hr"))) { return WidgetSpan( @@ -438,17 +438,16 @@ class StyledElementBuiltIn extends HtmlExtension { 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) && child.key.element?.localName != "html" && child.key.element?.localName != "body") const TextSpan(text: "\n", style: TextStyle(fontSize: 0)), @@ -464,7 +463,7 @@ class StyledElementBuiltIn extends HtmlExtension { .expandIndexed((index, child) => [ child.value, if (context.parser.shrinkWrap && - child.key.style.display == Display.block && + (child.key.style.display?.isBlock ?? false) && index != context.styledElement!.children.length - 1 && child.key.element?.parent?.localName != "th" && child.key.element?.parent?.localName != "td" && diff --git a/lib/src/css_box_widget.dart b/lib/src/css_box_widget.dart index 85679ea4b7..f907b323e5 100644 --- a/lib/src/css_box_widget.dart +++ b/lib/src/css_box_widget.dart @@ -159,10 +159,7 @@ class CssBoxWidget extends StatelessWidget { /// 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( @@ -424,42 +421,62 @@ class RenderCSSBox extends RenderBox } } - static double getIntrinsicDimension(RenderBox? firstChild, - double Function(RenderBox child) mainChildSizeGetter) { + static double getIntrinsicDimension( + 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)); + } catch (_) { + // See https://github.com/flutter/flutter/issues/65895 + debugPrint( + "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; } @override double computeMinIntrinsicWidth(double height) { return getIntrinsicDimension( - firstChild, (RenderBox child) => child.getMinIntrinsicWidth(height)); + firstChild, + (RenderBox child) => child.getMinIntrinsicWidth(height), + _calculateIntrinsicMargins().horizontal, + ); } @override double computeMaxIntrinsicWidth(double height) { return getIntrinsicDimension( - firstChild, (RenderBox child) => child.getMaxIntrinsicWidth(height)); + firstChild, + (RenderBox child) => child.getMaxIntrinsicWidth(height), + _calculateIntrinsicMargins().horizontal, + ); } @override double computeMinIntrinsicHeight(double width) { return getIntrinsicDimension( - firstChild, (RenderBox child) => child.getMinIntrinsicHeight(width)); + firstChild, + (RenderBox child) => child.getMinIntrinsicHeight(width), + _calculateIntrinsicMargins().vertical, + ); } @override double computeMaxIntrinsicHeight(double width) { return getIntrinsicDimension( - firstChild, (RenderBox child) => child.getMaxIntrinsicHeight(width)); + firstChild, + (RenderBox child) => child.getMaxIntrinsicHeight(width), + _calculateIntrinsicMargins().vertical, + ); } @override @@ -521,31 +538,20 @@ class RenderCSSBox extends RenderBox //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; + } else if (display.isBlock) { + width = (shrinkWrap || childIsReplaced) + ? childSize.width + horizontalMargins + : containingBlockSize.width; + height = childSize.height + verticalMargins; + } else { + width = childSize.width + horizontalMargins; + height = childSize.height; } return _Sizes(constraints.constrain(Size(width, height)), childSize); @@ -575,26 +581,14 @@ class RenderCSSBox extends RenderBox 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); @@ -628,7 +622,7 @@ class RenderCSSBox extends RenderBox 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); @@ -737,6 +731,40 @@ class RenderCSSBox extends RenderBox ); } + Margins _calculateIntrinsicMargins() { + //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); + + Margin marginLeft = margins.left!; + Margin marginRight = margins.right!; + + bool marginLeftIsAuto = marginLeft.unit == Unit.auto; + bool marginRightIsAuto = marginRight.unit == Unit.auto; + + if (display.isBlock) { + if (marginLeftIsAuto) { + marginLeft = Margin(0); + } + + if (marginRightIsAuto) { + marginRight = Margin(0); + } + } else { + marginLeft = Margin(0); + marginRight = Margin(0); + } + + return Margins( + left: marginLeft, + right: marginRight, + top: margins.top, + bottom: margins.bottom, + ); + } + @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { return defaultHitTestChildren(result, position: position); diff --git a/lib/src/processing/margins.dart b/lib/src/processing/margins.dart index fb1378912c..2598fcb096 100644 --- a/lib/src/processing/margins.dart +++ b/lib/src/processing/margins.dart @@ -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; } @@ -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 ?? diff --git a/lib/src/style.dart b/lib/src/style.dart index d5d1e0b72d..059bcb4e3f 100644 --- a/lib/src/style.dart +++ b/lib/src/style.dart @@ -689,14 +689,17 @@ enum VerticalAlign { /// 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. - if(display != Display.inline && display != Display.inlineBlock) { + // 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) { + switch (this) { case VerticalAlign.baseline: case VerticalAlign.sub: case VerticalAlign.sup: @@ -708,7 +711,6 @@ enum VerticalAlign { case VerticalAlign.middle: return PlaceholderAlignment.middle; } - } } diff --git a/lib/src/style/display.dart b/lib/src/style/display.dart index 77914d4c50..b69c2100c9 100644 --- a/lib/src/style/display.dart +++ b/lib/src/style/display.dart @@ -3,19 +3,19 @@ /// (https://www.w3.org/TR/css-display-3/#the-display-properties) enum Display { /// Equivalent to css `display: none;` - none ( + none( displayBox: DisplayBox.none, ), /// Equivalent to css `display: contents;` /// /// Not supported by flutter_html - contents ( + contents( displayBox: DisplayBox.contents, ), /// Equivalent to css `display: block;` - block ( + block( displayOutside: DisplayOutside.block, displayInside: DisplayInside.flow, ), @@ -23,19 +23,19 @@ enum Display { /// Equivalent to css `display: flow-root;` /// /// Not supported by flutter_html - flowRoot ( + flowRoot( displayOutside: DisplayOutside.block, displayInside: DisplayInside.flowRoot, ), /// Equivalent to css `display: inline;` - inline ( + inline( displayOutside: DisplayOutside.inline, displayInside: DisplayInside.flow, ), /// Equivalent to css `display: inline-block;` - inlineBlock ( + inlineBlock( displayOutside: DisplayOutside.inline, displayInside: DisplayInside.flowRoot, ), @@ -43,20 +43,20 @@ enum Display { /// Equivalent to css `display: run-in;` /// /// Not supported by flutter_html - runIn ( + runIn( displayOutside: DisplayOutside.runIn, displayInside: DisplayInside.flow, ), /// Equivalent to css `display: list-item;` - listItem ( + listItem( displayOutside: DisplayOutside.block, displayInside: DisplayInside.flow, displayListItem: true, ), /// Equivalent to css `display: inline list-item;` - inlineListItem ( + inlineListItem( displayOutside: DisplayOutside.inline, displayInside: DisplayInside.flow, displayListItem: true, @@ -65,7 +65,7 @@ enum Display { /// Equivalent to css `display: flex;` /// /// Not supported by flutter_html - flex ( + flex( displayOutside: DisplayOutside.block, displayInside: DisplayInside.flex, ), @@ -73,7 +73,7 @@ enum Display { /// Equivalent to css `display: inline-flex;` /// /// Not supported by flutter_html - inlineFlex ( + inlineFlex( displayOutside: DisplayOutside.inline, displayInside: DisplayInside.flex, ), @@ -81,7 +81,7 @@ enum Display { /// Equivalent to css `display: grid;` /// /// Not supported by flutter_html - grid ( + grid( displayOutside: DisplayOutside.block, displayInside: DisplayInside.grid, ), @@ -89,96 +89,96 @@ enum Display { /// Equivalent to css `display: inline-grid;` /// /// Not supported by flutter_html - inlineGrid ( + inlineGrid( displayOutside: DisplayOutside.inline, displayInside: DisplayInside.grid, ), /// Equivalent to css `display: ruby;` - ruby ( + ruby( displayOutside: DisplayOutside.inline, displayInside: DisplayInside.ruby, ), /// Equivalent to css `display: block ruby;` - blockRuby ( + blockRuby( displayOutside: DisplayOutside.block, displayInside: DisplayInside.ruby, ), /// Equivalent to css `display: table;` - table ( + table( displayOutside: DisplayOutside.block, displayInside: DisplayInside.table, ), /// Equivalent to css `display: inline-table;` - inlineTable ( + inlineTable( displayOutside: DisplayOutside.inline, displayInside: DisplayInside.table, ), /// Equivalent to css `display: table-row-group;` - tableRowGroup ( + tableRowGroup( displayInternal: DisplayInternal.tableRowGroup, ), /// Equivalent to css `display: table-header-group;` - tableHeaderGroup ( + tableHeaderGroup( displayInternal: DisplayInternal.tableHeaderGroup, ), /// Equivalent to css `display: table-footer-group;` - tableFooterGroup ( + tableFooterGroup( displayInternal: DisplayInternal.tableFooterGroup, ), /// Equivalent to css `display: table-row;` - tableRow ( + tableRow( displayInternal: DisplayInternal.tableRowGroup, ), /// Equivalent to css `display: table-cell;` - tableCell ( + tableCell( displayInternal: DisplayInternal.tableCell, displayInside: DisplayInside.flowRoot, ), /// Equivalent to css `display: table-column-group;` - tableColumnGroup ( + tableColumnGroup( displayInternal: DisplayInternal.tableColumnGroup, ), /// Equivalent to css `display: table-column;` - tableColumn ( + tableColumn( displayInternal: DisplayInternal.tableColumn, ), /// Equivalent to css `display: table-caption;` - tableCaption ( + tableCaption( displayInternal: DisplayInternal.tableCaption, displayInside: DisplayInside.flowRoot, ), /// Equivalent to css `display: ruby-base;` - rubyBase ( + rubyBase( displayInternal: DisplayInternal.rubyBase, displayInside: DisplayInside.flow, ), /// Equivalent to css `display: ruby-text;` - rubyText ( + rubyText( displayInternal: DisplayInternal.rubyText, displayInside: DisplayInside.flow, ), /// Equivalent to css `display: ruby-base-container;` - rubyBaseContainer ( + rubyBaseContainer( displayInternal: DisplayInternal.rubyBaseContainer, ), /// Equivalent to css `display: ruby-text-container;` - rubyTextContainer ( + rubyTextContainer( displayInternal: DisplayInternal.rubyTextContainer, ); @@ -236,4 +236,4 @@ enum DisplayInternal { enum DisplayBox { contents, // not supported none, -} \ No newline at end of file +} diff --git a/lib/src/style/margin.dart b/lib/src/style/margin.dart index d2cbe157d1..257fa0b296 100644 --- a/lib/src/style/margin.dart +++ b/lib/src/style/margin.dart @@ -63,6 +63,19 @@ class Margins { blockStart?.unit == Unit.auto ? blockStart : Margin(0, Unit.px)); } + /// The total margin in the horizontal direction. + double get horizontal => + (left?.value ?? inlineStart?.value ?? 0) + + (right?.value ?? inlineEnd?.value ?? 0); + + /// The total margin in the vertical direction. + double get vertical => + (top?.value ?? blockStart?.value ?? 0) + + (bottom?.value ?? blockEnd?.value ?? 0); + + /// The size that this [Margins] would occupy with an empty interior. + Size get collapsedSize => Size(horizontal, vertical); + Margins copyWith({ Margin? left, Margin? right, diff --git a/packages/flutter_html_table/lib/flutter_html_table.dart b/packages/flutter_html_table/lib/flutter_html_table.dart index f7e928c6c7..0ec6607b39 100644 --- a/packages/flutter_html_table/lib/flutter_html_table.dart +++ b/packages/flutter_html_table/lib/flutter_html_table.dart @@ -39,7 +39,7 @@ class TableHtmlExtension extends HtmlExtension { elementClasses: context.classes.toList(), tableStructure: children, cellDescendants: cellDescendants, - style: Style(display: Display.block), + style: Style(display: Display.table), node: context.node, ); } @@ -51,9 +51,11 @@ class TableHtmlExtension extends HtmlExtension { fontWeight: FontWeight.bold, textAlign: TextAlign.center, verticalAlign: VerticalAlign.middle, + display: Display.tableCell, ) : Style( verticalAlign: VerticalAlign.middle, + display: Display.tableCell, ), children: children, node: context.node, @@ -71,7 +73,13 @@ class TableHtmlExtension extends HtmlExtension { elementId: context.id, elementClasses: context.classes.toList(), children: children, - style: Style(), + style: Style( + display: context.elementName == "thead" + ? Display.tableHeaderGroup + : context.elementName == "tfoot" + ? Display.tableFooterGroup + : Display.tableRowGroup, + ), node: context.node, ); } @@ -82,7 +90,9 @@ class TableHtmlExtension extends HtmlExtension { elementId: context.id, elementClasses: context.classes.toList(), children: children, - style: Style(), + style: Style( + display: Display.tableRow, + ), node: context.node, ); } @@ -93,7 +103,11 @@ class TableHtmlExtension extends HtmlExtension { elementId: context.id, elementClasses: context.classes.toList(), children: children, - style: Style(), + style: Style( + display: context.elementName == "col" + ? Display.tableColumn + : Display.tableColumnGroup, + ), node: context.node, ); } @@ -106,7 +120,7 @@ class TableHtmlExtension extends HtmlExtension { if (context.elementName == "table") { return WidgetSpan( child: CssBoxWidget( - style: context.styledElement!.style, + style: context.style!, child: LayoutBuilder( builder: (_, constraints) { return _layoutCells( @@ -125,6 +139,7 @@ class TableHtmlExtension extends HtmlExtension { child: CssBoxWidget.withInlineSpanChildren( children: context.inlineSpanChildren!, style: Style(), + childIsReplaced: true, ), ); } @@ -231,28 +246,31 @@ Widget _layoutCells( columni += columnColspanOffset[columni].clamp(1, columnMax - columni - 1); } + + final colspan = min(child.colspan, columnMax - columni); + final rowspan = min(child.rowspan, rows.length - rowi); + cells.add(GridPlacement( columnStart: columni, - columnSpan: min(child.colspan, columnMax - columni), + columnSpan: colspan, rowStart: rowi, - rowSpan: min(child.rowspan, rows.length - rowi), + rowSpan: rowspan, child: CssBoxWidget( style: child.style.merge(row.style), - child: Builder(builder: (context) { - final alignment = - child.style.direction ?? Directionality.of(context); - return SizedBox.expand( - child: Container( - alignment: _getCellAlignment(child, alignment), - child: CssBoxWidget.withInlineSpanChildren( - children: [ - parsedCells[child] ?? const TextSpan(text: "error") - ], - style: Style(), - ), + child: SizedBox.expand( + child: Container( + alignment: _getCellAlignment( + child, + child.style.direction ?? + Directionality.of(context.buildContext!)), + child: CssBoxWidget.withInlineSpanChildren( + children: [ + parsedCells[child] ?? const TextSpan(text: "error") + ], + style: Style(), ), - ); - }), + ), + ), ), )); columnRowOffset[columni] = child.rowspan - 1; @@ -281,6 +299,9 @@ Widget _layoutCells( gridFit: GridFit.loose, columnSizes: finalColumnSizes, rowSizes: rowSizes, + // TODO add style option for border-spacing + // rowGap: 2, + // columnGap: 2, children: cells, ); } diff --git a/test/style/css_parsing/margin_test.dart b/test/style/css_parsing/margin_test.dart index 0890cc8e98..67728bff5c 100644 --- a/test/style/css_parsing/margin_test.dart +++ b/test/style/css_parsing/margin_test.dart @@ -156,8 +156,8 @@ void main() { ), ), ); - expect(_getMargin("Test"), - equals(Margins.only(bottom: 8, blockEnd: 8, unit: Unit.px))); + expect( + _getMargin("Test"), equals(Margins.only(blockEnd: 8, unit: Unit.px))); }, ); @@ -291,8 +291,8 @@ void main() { ), ), ); - expect(_getMargin("Test"), - equals(Margins.only(bottom: 8, blockEnd: 8, unit: Unit.px))); + expect( + _getMargin("Test"), equals(Margins.only(blockEnd: 8, unit: Unit.px))); }, );