Skip to content

Commit

Permalink
Re-implement table layout (#365)
Browse files Browse the repository at this point in the history
* Drop `flutter_layout_grid` dependency
* Separate border support into its own `StyleBorder`
* Add support for `border-collapse: collapse`
* Add support for rowspan=0
* Add support for `box-sizing`
* Add support for `valign`
  • Loading branch information
daohoangson authored Jan 18, 2021
1 parent 3b54a3b commit e2e2897
Show file tree
Hide file tree
Showing 67 changed files with 2,677 additions and 1,863 deletions.
23 changes: 4 additions & 19 deletions demo_app/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -203,27 +203,12 @@
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework",
"${PODS_ROOT}/../Flutter/Flutter.framework",
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
"${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework",
"${BUILT_PRODUCTS_DIR}/video_player/video_player.framework",
"${BUILT_PRODUCTS_DIR}/wakelock/wakelock.framework",
"${BUILT_PRODUCTS_DIR}/webview_flutter/webview_flutter.framework",
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/webview_flutter.framework",
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
Expand Down
21 changes: 14 additions & 7 deletions demo_app/test/goldens.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,19 @@
"inline/LI_padding-inline-start": "<ul style=\"padding-inline-start: 9px\">\n <li style=\"padding-inline-start: 19px\">19px</li>\n <li style=\"padding-inline-start: 29px\">29px</li>\n <li>9px</li>\n<ul>\n",
"inline/background-color/block": "<div style=\"background-color: #f00\"><div>Foo</div></div>",
"inline/background-color/inline": "Foo <span style=\"background-color: #f00\">bar</span>",
"inline/border/border-bottom": "<span style=\"border-bottom: 1px\">Foo</span>",
"inline/border/border-top": "<span style=\"border-top: 1px\">Foo</span>",
"inline/border/dashed": "<span style=\"border-top: 1px dashed\">Foo</span>",
"inline/border/dotted": "<span style=\"border-top: 1px dotted\">Foo</span>",
"inline/border/double": "<span style=\"border-bottom: 1px double\">Foo</span>",
"inline/border/solid": "<span style=\"border-bottom: 1px solid\">Foo</span>",
"inline/border/border-block-end": "<div style=\"border-block-end: 1px solid\">Foo</div>",
"inline/border/border-block-start": "<div style=\"border-block-start: 1px solid\">Foo</div>",
"inline/border/border-bottom": "<div style=\"border-bottom: 1px solid\">Foo</div>",
"inline/border/border-inline-end": "<div style=\"border-inline-end: 1px solid\">Foo</div>",
"inline/border/border-inline-start": "<div style=\"border-inline-start: 1px solid\">Foo</div>",
"inline/border/border-left": "<div style=\"border-left: 1px solid\">Foo</div>",
"inline/border/border-right": "<div style=\"border-right: 1px solid\">Foo</div>",
"inline/border/border-top": "<div style=\"border-top: 1px solid\">Foo</div>",
"inline/border/box-sizing--border-box": "<div style=\"border: 1px solid; box-sizing: border-box\">Foo</div>",
"inline/border/color": "<div style=\"border: 1px solid red\">Foo</div>",
"inline/border/solid": "<div style=\"border: 1px solid\">Foo</div>",
"inline/border/with_text": "Foo <span style=\"border: 1px solid\">bar</span>",
"inline/border/with_text_multiline": "<p>Foo</p>\n<span style=\"border: 1px solid\">bar</span>",
"inline/color": "<span style=\"color: #F00\">red</span>\n<span style=\"color: #0F08\">red 53%</span>\n<span style=\"color: #00FF00\">green</span>\n<span style=\"color: #00FF0080\">green 50%</span>\n",
"inline/display/block": "<div>1 <span style=\"display: block\">2</span></div>",
"inline/display/div": "<div>1 <div>2</div></div>",
Expand Down Expand Up @@ -199,4 +206,4 @@
"tag/OL/type_upper-roman": "<ol type=\"I\"><li>One</li><li>Two</li><li>Three</li><ol>",
"tag/TABLE/colspan": "<table border=\"1\">\n <caption>Source: <a href=\"https://www.w3schools.com/tags/att_td_colspan.asp\">w3schools</a></caption>\n <tr>\n <th>Month</th>\n <th>Savings</th>\n </tr>\n <tr>\n <td>January</td>\n <td>$100</td>\n </tr>\n <tr>\n <td>February</td>\n <td>$80</td>\n </tr>\n <tr>\n <td colspan=\"2\">Sum: $180</td>\n </tr>\n</table>",
"tag/TABLE/rowspan": "<table border=\"1\">\n <caption>Source: <a href=\"https://www.w3schools.com/tags/att_td_colspan.asp\">w3schools</a></caption>\n <tr>\n <th>Month</th>\n <th>Savings</th>\n <th>Savings for holiday!</th>\n </tr>\n <tr>\n <td>January</td>\n <td>$100</td>\n <td rowspan=\"2\">$50</td>\n </tr>\n <tr>\n <td>February</td>\n <td>$80</td>\n </tr>\n</table>"
}
}
Binary file modified demo_app/test/goldens/TABLE,CAPTION,TBODY,THEAD,TFOOT,TR,TH,TD.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo_app/test/goldens/inline/border/border-bottom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo_app/test/goldens/inline/border/border-top.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo_app/test/goldens/inline/border/color.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed demo_app/test/goldens/inline/border/dashed.png
Binary file not shown.
Binary file removed demo_app/test/goldens/inline/border/dotted.png
Binary file not shown.
Binary file removed demo_app/test/goldens/inline/border/double.png
Binary file not shown.
Binary file modified demo_app/test/goldens/inline/border/solid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo_app/test/goldens/inline/border/with_text.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo_app/test/goldens/tag/TABLE/colspan.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo_app/test/goldens/tag/TABLE/rowspan.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo_app/test/table/aspect_ratio_img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo_app/test/table/collapsed_border.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo_app/test/table/colspan.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo_app/test/table/rowspan.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo_app/test/table/table_in_list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo_app/test/table/table_in_table.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo_app/test/table/valign_baseline_1a.png
Binary file added demo_app/test/table/valign_baseline_1b.png
Binary file added demo_app/test/table/valign_baseline_1c.png
Binary file added demo_app/test/table/valign_baseline_2.png
Binary file added demo_app/test/table/valign_baseline_3.png
6 changes: 3 additions & 3 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ Below tags are the ones that have special meaning / styling, all other tags will
- Attributes: `type`, `start`, `reversed`
- Inline style `list-style-type` values: `lower-alpha`, `upper-alpha`, `lower-latin`, `upper-latin`, `circle`, `decimal`, `disc`, `lower-roman`, `upper-roman`, `square`
- TABLE/CAPTION/THEAD/TBODY/TFOOT/TR/TD/TH with support for:
- TABLE attributes (`border`, `cellpadding`) and inline style (`border`)
- TD/TH attributes `colspan`, `rowspan` are parsed but ignored during rendering, use [flutter_widget_from_html](https://pub.dev/packages/flutter_widget_from_html) if you need them
- TABLE attributes `border`, `cellpadding`, `cellspacing`
- TD/TH attributes `colspan`, `rowspan`, `valign`
- ABBR, ACRONYM, ADDRESS, ARTICLE, ASIDE, B, BIG, BLOCKQUOTE, BR, CENTER, CITE, CODE,
DD, DEL, DFN, DIV, DL, DT, EM, FIGCAPTION, FIGURE, FONT, FOOTER, HEADER, HR, I, IMG, INS,
KBD, MAIN, NAV, P, PRE, Q, RP, RT, RUBY, S, SAMP, SECTION, STRIKE, STRONG, SUB, SUP, TT, U, VAR
Expand All @@ -108,7 +108,7 @@ These tags and their contents will be ignored:
### Inline stylings

- background (color only), background-color: hex values, `rgb()`, `hsl()` or named colors
- border-top, border-bottom: overline/underline with support for dashed/dotted/double/solid style
- border, border-xxx and box-sizing
- color: hex values, `rgb()`, `hsl()` or named colors
- direction (similar to `dir` attribute)
- font-family
Expand Down
8 changes: 5 additions & 3 deletions packages/core/lib/src/core_data.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'dart:math';

import 'package:flutter/widgets.dart';
import 'package:html/dom.dart' as dom;

Expand All @@ -9,7 +7,6 @@ import 'core_widget_factory.dart';
part 'data/build_bits.dart';
part 'data/css.dart';
part 'data/image.dart';
part 'data/table.dart';
part 'data/text_style.dart';

/// A building element metadata.
Expand Down Expand Up @@ -79,6 +76,11 @@ abstract class BuildMetadata {
/// A building operation to customize how a DOM element is rendered.
@immutable
class BuildOp {
/// The recommended maximum value for [priority].
///
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
static const kPriorityMax = 9007199254740991;

/// The execution priority, op with lower priority will run first.
///
/// Default: 10.
Expand Down
1 change: 1 addition & 0 deletions packages/core/lib/src/core_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'core_html_widget.dart';

export 'widgets/css_sizing.dart';
export 'widgets/html_ruby.dart';
export 'widgets/html_table.dart';

/// The default character threshold to build widget tree asynchronously.
///
Expand Down
130 changes: 54 additions & 76 deletions packages/core/lib/src/core_widget_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'core_html_widget.dart';
class WidgetFactory {
BuildOp _styleBgColor;
BuildOp _styleBlock;
BuildOp _styleBorder;
BuildOp _styleDisplayNone;
BuildOp _styleMargin;
BuildOp _stylePadding;
Expand All @@ -31,17 +32,41 @@ class WidgetFactory {

HtmlWidget get _widget => _state?.widget;

/// Builds [Align].
Widget buildAlign(
BuildMetadata meta, Widget child, AlignmentGeometry alignment) =>
alignment == null ? child : Align(alignment: alignment, child: child);

/// Builds [AspectRatio].
Widget buildAspectRatio(
BuildMetadata meta, Widget child, double aspectRatio) =>
child != null && aspectRatio != null
? AspectRatio(aspectRatio: aspectRatio, child: child)
: null;
aspectRatio == null
? child
: AspectRatio(aspectRatio: aspectRatio, child: child);

/// Builds primary column (body).
WidgetPlaceholder buildBody(BuildMetadata meta, Iterable<Widget> children) =>
buildColumnPlaceholder(meta, children, trimMarginVertical: true);

/// Builds [border] with [Container] or [DecoratedBox].
///
/// See https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing
/// for more information regarding `content-box` (the default)
/// and `border-box` (set [isBorderBox] to use).
Widget buildBorder(BuildMetadata meta, Widget child, BoxBorder border,
{bool isBorderBox = false}) =>
border == null
? child
: isBorderBox == true
? DecoratedBox(
child: child,
decoration: BoxDecoration(border: border),
)
: Container(
child: child,
decoration: BoxDecoration(border: border),
);

/// Builds column placeholder.
WidgetPlaceholder buildColumnPlaceholder(
BuildMetadata meta,
Expand All @@ -51,14 +76,13 @@ class WidgetFactory {
if (children?.isNotEmpty != true) return null;

if (children.length == 1) {
final first = children.first;
if (first is WidgetPlaceholder) {
if (first is! ColumnPlaceholder) return first;

final existingPlaceholder = first as ColumnPlaceholder;
if (existingPlaceholder.trimMarginVertical == trimMarginVertical) {
return first;
final child = children.first;
if (child is ColumnPlaceholder) {
if (child.trimMarginVertical == trimMarginVertical) {
return child;
}
} else {
return child;
}
}

Expand Down Expand Up @@ -108,7 +132,7 @@ class WidgetFactory {
/// Builds [GestureDetector].
Widget buildGestureDetector(
BuildMetadata meta, Widget child, GestureTapCallback onTap) =>
GestureDetector(child: child, onTap: onTap);
onTap == null ? child : GestureDetector(child: child, onTap: onTap);

/// Builds horizontal scroll view.
Widget buildHorizontalScrollView(BuildMetadata meta, Widget child) =>
Expand Down Expand Up @@ -144,9 +168,9 @@ class WidgetFactory {

/// Builds [Padding].
Widget buildPadding(BuildMetadata meta, Widget child, EdgeInsets padding) =>
child != null && padding != null && padding != const EdgeInsets.all(0)
? Padding(child: child, padding: padding)
: child;
padding == null || padding == const EdgeInsets.all(0)
? child
: Padding(child: child, padding: padding);

/// Builds [Stack].
Widget buildStack(
Expand All @@ -157,39 +181,6 @@ class WidgetFactory {
textDirection: tsh.textDirection,
);

/// Builds [Table].
Widget buildTable(BuildMetadata meta, TextStyleHtml tsh, TableMetadata data) {
final rows = <TableRow>[];
final slotIndices = <int>[];
final tableCols = data.cols;
final tableRows = data.rows;

for (var r = 0; r < tableRows; r++) {
final cells = List<Widget>(tableCols);
for (var c = 0; c < tableCols; c++) {
final index = data.getIndexAt(row: r, column: c);
if (index == -1 || slotIndices.contains(index)) {
cells[c] = widget0;
continue;
}
slotIndices.add(index);

cells[c] = TableCell(child: data.getWidgetAt(index));
}

if (cells.isEmpty) continue;
rows.add(TableRow(children: cells));
}

if (rows.isEmpty) return null;

final tableBorder = data.border != null
// TODO: support different styling for border sides
? TableBorder.symmetric(inside: data.border, outside: data.border)
: null;
return Table(border: tableBorder, children: rows);
}

/// Builds [RichText].
Widget buildText(BuildMetadata meta, TextStyleHtml tsh, InlineSpan text) =>
RichText(
Expand Down Expand Up @@ -581,11 +572,20 @@ class WidgetFactory {
case kTagTable:
meta
..[kCssDisplay] = kCssDisplayTable
..register(TagTable.borderOp(
tryParseDoubleFromMap(attrs, kAttributeBorder) ?? 0.0,
tryParseDoubleFromMap(attrs, kAttributeCellSpacing) ?? 2.0,
))
..register(TagTable.cellPaddingOp(
tryParseDoubleFromMap(attrs, kAttributeCellPadding) ?? 1));
tryParseDoubleFromMap(attrs, kAttributeCellPadding) ?? 1.0));
break;
case kTagTableCell:
meta[kCssVerticalAlign] = kCssVerticalAlignMiddle;
break;
case kTagTableHeaderCell:
meta.tsb(TextStyleOps.fontWeight, FontWeight.bold);
meta
..[kCssVerticalAlign] = kCssVerticalAlignMiddle
..tsb(TextStyleOps.fontWeight, FontWeight.bold);
break;
case kTagTableCaption:
meta[kCssTextAlign] = kCssTextAlignCenter;
Expand Down Expand Up @@ -613,33 +613,6 @@ class WidgetFactory {
meta.register(_styleBgColor);
break;

case kCssBorderBottom:
final borderBottom = tryParseCssBorderSide(value);
if (borderBottom != null) {
meta.register(TextStyleOps.textDecoOp(TextDeco(
color: borderBottom.color,
under: true,
style: borderBottom.style,
thickness: borderBottom.width,
)));
} else {
meta.register(TextStyleOps.textDecoOp(TextDeco(under: false)));
}
break;
case kCssBorderTop:
final borderTop = tryParseCssBorderSide(value);
if (borderTop != null) {
meta.register(TextStyleOps.textDecoOp(TextDeco(
color: borderTop.color,
over: true,
style: borderTop.style,
thickness: borderTop.width,
)));
} else {
meta.register(TextStyleOps.textDecoOp(TextDeco(over: false)));
}
break;

case kCssColor:
final color = tryParseColor(value);
if (color != null) meta.tsb(TextStyleOps.color, color);
Expand Down Expand Up @@ -722,6 +695,11 @@ class WidgetFactory {
break;
}

if (key.startsWith(kCssBorder)) {
_styleBorder ??= StyleBorder(this).buildOp;
meta.register(_styleBorder);
}

if (key.startsWith(kCssMargin)) {
_styleMargin ??= StyleMargin(this).buildOp;
meta.register(_styleMargin);
Expand Down
Loading

1 comment on commit e2e2897

@github-actions
Copy link

Choose a reason for hiding this comment

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

Please sign in to comment.