Support GitHub Flavored Markdown (#133)

diff --git a/packages/flutter_markdown/example/lib/main.dart b/packages/flutter_markdown/example/lib/main.dart
index ee3a69d..1c7a2aa 100644
--- a/packages/flutter_markdown/example/lib/main.dart
+++ b/packages/flutter_markdown/example/lib/main.dart
@@ -8,6 +8,61 @@
 const String _markdownData = """# Markdown Example
 Markdown allows you to easily include formatted text, images, and even formatted Dart code in your app.
 
+## Titles
+
+Setext-style
+
+```
+This is an H1
+=============
+
+This is an H2
+-------------
+```
+
+Atx-style
+
+```
+# This is an H1
+
+## This is an H2
+
+###### This is an H6
+```
+
+Select the valid headers:
+
+- [x] `# hello`
+- [ ] `#hello`
+
+## Links
+
+[Google's Homepage][Google]
+
+```
+[inline-style](https://www.google.com)
+
+[reference-style][Google]
+```
+
+## Images
+
+![Flutter logo](/dart-lang/site-shared/master/src/_assets/image/flutter/icon/64.png)
+
+## Tables
+
+|Syntax                                 |Result                               |
+|---------------------------------------|-------------------------------------|
+|`*italic 1*`                           |*italic 1*                           |
+|`_italic 2_`                           | _italic 2_                          |
+|`**bold 1**`                           |**bold 1**                           |
+|`__bold 2__`                           |__bold 2__                           |
+|`This is a ~~strikethrough~~`          |This is a ~~strikethrough~~          |
+|`***italic bold 1***`                  |***italic bold 1***                  |
+|`___italic bold 2___`                  |___italic bold 2___                  |
+|`***~~italic bold strikethrough 1~~***`|***~~italic bold strikethrough 1~~***|
+|`~~***italic bold strikethrough 2***~~`|~~***italic bold strikethrough 2***~~|
+
 ## Styling
 Style text as _italic_, __bold__, ~~strikethrough~~, or `inline code`.
 
@@ -15,21 +70,6 @@
 - To better clarify
 - Your points
 
-## Links
-You can use [hyperlinks](hyperlink) in markdown
-
-## Images
-
-You can include images:
-
-![Flutter logo](https://raw.githubusercontent.com/dart-lang/site-shared/master/src/_assets/image/flutter/icon/64.png)
-
-## Markdown widget
-
-This is an example of how to create your own Markdown widget:
-
-    Markdown(data: 'Hello _world_!');
-
 ## Code blocks
 Formatted Dart code looks really pretty too:
 
@@ -43,7 +83,15 @@
 }
 ```
 
+## Markdown widget
+
+This is an example of how to create your own Markdown widget:
+
+    Markdown(data: 'Hello _world_!');
+
 Enjoy!
+
+[Google]: https://www.google.com/
 """;
 
 void main() {
@@ -51,7 +99,10 @@
     title: "Markdown Demo",
     home: Scaffold(
       appBar: AppBar(title: const Text('Markdown Demo')),
-      body: const Markdown(data: _markdownData),
+      body: Markdown(
+        data: _markdownData,
+        imageDirectory: 'https://raw.githubusercontent.com',
+      ),
     ),
   ));
 }
diff --git a/packages/flutter_markdown/lib/src/builder.dart b/packages/flutter_markdown/lib/src/builder.dart
index 42dda80..c6bc4e6 100644
--- a/packages/flutter_markdown/lib/src/builder.dart
+++ b/packages/flutter_markdown/lib/src/builder.dart
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:io';
+import 'dart:io' show File;
 
 import 'package:flutter/gestures.dart';
-import 'package:flutter/widgets.dart';
+import 'package:flutter/material.dart';
 import 'package:markdown/markdown.dart' as md;
-import 'package:path/path.dart' as p;
 
 import 'style_sheet.dart';
+import 'widget.dart';
 
-final Set<String> _kBlockTags = Set<String>.from(<String>[
+const List<String> _kBlockTags = const <String>[
   'p',
   'h1',
   'h2',
@@ -25,7 +25,11 @@
   'ol',
   'ul',
   'hr',
-]);
+  'table',
+  'thead',
+  'tbody',
+  'tr'
+];
 
 const List<String> _kListTags = const <String>['ul', 'ol'];
 
@@ -42,6 +46,10 @@
   int nextListIndex = 0;
 }
 
+class _TableElement {
+  final List<TableRow> rows = <TableRow>[];
+}
+
 /// A collection of widgets that should be placed adjacent to (inline with)
 /// other inline elements in the same parent block.
 ///
@@ -85,7 +93,13 @@
 ///  * [Markdown], which is a widget that parses and displays Markdown.
 class MarkdownBuilder implements md.NodeVisitor {
   /// Creates an object that builds a [Widget] tree from parsed Markdown.
-  MarkdownBuilder({this.delegate, this.styleSheet, this.imageDirectory});
+  MarkdownBuilder({
+    this.delegate,
+    this.styleSheet,
+    this.imageDirectory,
+    this.imageBuilder,
+    this.checkboxBuilder,
+  });
 
   /// A delegate that controls how link and `pre` elements behave.
   final MarkdownBuilderDelegate delegate;
@@ -93,11 +107,18 @@
   /// Defines which [TextStyle] objects to use for each type of element.
   final MarkdownStyleSheet styleSheet;
 
-  /// The base directory holding images referenced by Img tags with local file paths.
-  final Directory imageDirectory;
+  /// The base directory holding images referenced by Img tags with local or network file paths.
+  final String imageDirectory;
+
+  /// Call when build an image widget.
+  final MarkdownImageBuilder imageBuilder;
+
+  /// Call when build a checkbox widget.
+  final MarkdownCheckboxBuilder checkboxBuilder;
 
   final List<String> _listIndents = <String>[];
   final List<_BlockElement> _blocks = <_BlockElement>[];
+  final List<_TableElement> _tables = <_TableElement>[];
   final List<_InlineElement> _inlines = <_InlineElement>[];
   final List<GestureRecognizer> _linkHandlers = <GestureRecognizer>[];
 
@@ -107,6 +128,7 @@
   List<Widget> build(List<md.Node> nodes) {
     _listIndents.clear();
     _blocks.clear();
+    _tables.clear();
     _inlines.clear();
     _linkHandlers.clear();
 
@@ -117,6 +139,7 @@
       node.accept(this);
     }
 
+    assert(_tables.isEmpty);
     assert(_inlines.isEmpty);
     return _blocks.single.children;
   }
@@ -128,18 +151,26 @@
 
     _addParentInlineIfNeeded(_blocks.last.tag);
 
-    final TextSpan span = _blocks.last.tag == 'pre'
-        ? delegate.formatText(styleSheet, text.text)
-        : TextSpan(
-            style: _inlines.last.style,
-            text: text.text,
-            recognizer: _linkHandlers.isNotEmpty ? _linkHandlers.last : null,
-          );
-
-    _inlines.last.children.add(RichText(
-      textScaleFactor: styleSheet.textScaleFactor,
-      text: span,
-    ));
+    Widget child;
+    if (_blocks.last.tag == 'pre') {
+      child = Scrollbar(
+        child: SingleChildScrollView(
+          scrollDirection: Axis.horizontal,
+          padding: styleSheet.codeblockPadding,
+          child: RichText(text: delegate.formatText(styleSheet, text.text)),
+        ),
+      );
+    } else {
+      child = RichText(
+        textScaleFactor: styleSheet.textScaleFactor,
+        text: TextSpan(
+          style: _inlines.last.style,
+          text: text.text,
+          recognizer: _linkHandlers.isNotEmpty ? _linkHandlers.last : null,
+        ),
+      );
+    }
+    _inlines.last.children.add(child);
   }
 
   @override
@@ -147,7 +178,19 @@
     final String tag = element.tag;
     if (_isBlockTag(tag)) {
       _addAnonymousBlockIfNeeded(styleSheet.styles[tag]);
-      if (_isListTag(tag)) _listIndents.add(tag);
+      if (_isListTag(tag)) {
+        _listIndents.add(tag);
+      } else if (tag == 'table') {
+        _tables.add(_TableElement());
+      } else if (tag == 'tr') {
+        final length = _tables.single.rows.length;
+        BoxDecoration decoration = styleSheet.tableCellsDecoration;
+        if (length == 0 || length % 2 == 1) decoration = null;
+        _tables.single.rows.add(TableRow(
+          decoration: decoration,
+          children: <Widget>[],
+        ));
+      }
       _blocks.add(_BlockElement(tag));
     } else {
       _addParentInlineIfNeeded(_blocks.last.tag);
@@ -190,17 +233,31 @@
         _listIndents.removeLast();
       } else if (tag == 'li') {
         if (_listIndents.isNotEmpty) {
+          Widget bullet;
+          dynamic el = element.children[0];
+          if (el is md.Element && el.attributes['type'] == 'checkbox') {
+            bool val = el.attributes['checked'] != 'false';
+            bullet = _buildCheckbox(val);
+          } else {
+            bullet = _buildBullet(_listIndents.last);
+          }
           child = Row(
-            crossAxisAlignment: CrossAxisAlignment.start,
             children: <Widget>[
               SizedBox(
                 width: styleSheet.listIndent,
-                child: _buildBullet(_listIndents.last),
+                child: bullet,
               ),
               Expanded(child: child)
             ],
           );
         }
+      } else if (tag == 'table') {
+        child = Table(
+          defaultColumnWidth: IntrinsicColumnWidth(),
+          defaultVerticalAlignment: TableCellVerticalAlignment.middle,
+          border: styleSheet.tableBorder,
+          children: _tables.removeLast().rows,
+        );
       } else if (tag == 'blockquote') {
         child = DecoratedBox(
           decoration: styleSheet.blockquoteDecoration,
@@ -212,10 +269,7 @@
       } else if (tag == 'pre') {
         child = DecoratedBox(
           decoration: styleSheet.codeblockDecoration,
-          child: Padding(
-            padding: styleSheet.codeblockPadding,
-            child: child,
-          ),
+          child: child,
         );
       } else if (tag == 'hr') {
         child = DecoratedBox(
@@ -232,6 +286,33 @@
       if (tag == 'img') {
         // create an image widget for this image
         current.children.add(_buildImage(element.attributes['src']));
+      } else if (tag == 'br') {
+        current.children.add(RichText(text: const TextSpan(text: '\n')));
+      } else if (tag == 'th' || tag == 'td') {
+        TextAlign align;
+        String style = element.attributes['style'];
+        if (style == null) {
+          align = tag == 'th' ? styleSheet.tableHeadAlign : TextAlign.left;
+        } else {
+          RegExp regExp = RegExp(r'text-align: (left|center|right)');
+          Match match = regExp.matchAsPrefix(style);
+          switch (match[1]) {
+            case 'left':
+              align = TextAlign.left;
+              break;
+            case 'center':
+              align = TextAlign.center;
+              break;
+            case 'right':
+              align = TextAlign.right;
+              break;
+          }
+        }
+        Widget child = _buildTableCell(
+          _mergeInlineChildren(current.children),
+          textAlign: align,
+        );
+        _tables.single.rows.last.children.add(child);
       } else if (tag == 'a') {
         _linkHandlers.removeLast();
       }
@@ -259,17 +340,23 @@
 
     Uri uri = Uri.parse(path);
     Widget child;
-    if (uri.scheme == 'http' || uri.scheme == 'https') {
+    if (imageBuilder != null) {
+      child = imageBuilder(uri);
+    } else if (uri.scheme == 'http' || uri.scheme == 'https') {
       child = Image.network(uri.toString(), width: width, height: height);
     } else if (uri.scheme == 'data') {
       child = _handleDataSchemeUri(uri, width, height);
     } else if (uri.scheme == "resource") {
       child = Image.asset(path.substring(9), width: width, height: height);
     } else {
-      String filePath = (imageDirectory == null
-          ? uri.toFilePath()
-          : p.join(imageDirectory.path, uri.toFilePath()));
-      child = Image.file(File(filePath), width: width, height: height);
+      Uri fileUri = imageDirectory != null
+          ? Uri.parse(imageDirectory + uri.toString())
+          : uri;
+      if (fileUri.scheme == 'http' || fileUri.scheme == 'https') {
+        child = Image.network(fileUri.toString(), width: width, height: height);
+      } else {
+        child = Image.file(File.fromUri(fileUri), width: width, height: height);
+      }
     }
 
     if (_linkHandlers.isNotEmpty) {
@@ -295,21 +382,49 @@
     return const SizedBox();
   }
 
+  Widget _buildCheckbox(bool checked) {
+    if (checkboxBuilder != null) {
+      return checkboxBuilder(checked);
+    }
+    return Padding(
+      padding: const EdgeInsets.only(right: 4),
+      child: Icon(
+        checked ? Icons.check_box : Icons.check_box_outline_blank,
+        size: styleSheet.checkbox.fontSize,
+        color: styleSheet.checkbox.color,
+      ),
+    );
+  }
+
   Widget _buildBullet(String listTag) {
-    if (listTag == 'ul')
+    if (listTag == 'ul') {
       return Text(
         '•',
         textAlign: TextAlign.center,
-        style: styleSheet.styles['li'],
+        style: styleSheet.listBullet,
       );
+    }
 
     final int index = _blocks.last.nextListIndex;
     return Padding(
-      padding: const EdgeInsets.only(right: 5.0),
+      padding: const EdgeInsets.only(right: 4),
       child: Text(
         '${index + 1}.',
         textAlign: TextAlign.right,
-        style: styleSheet.styles['li'],
+        style: styleSheet.listBullet,
+      ),
+    );
+  }
+
+  Widget _buildTableCell(List<Widget> children, {TextAlign textAlign}) {
+    return TableCell(
+      child: Padding(
+        padding: styleSheet.tableCellsPadding,
+        child: DefaultTextStyle(
+          style: styleSheet.tableBody,
+          textAlign: textAlign,
+          child: Wrap(children: children),
+        ),
       ),
     );
   }
@@ -337,8 +452,9 @@
 
     final _InlineElement inline = _inlines.single;
     if (inline.children.isNotEmpty) {
-      List<Widget> mergedInlines = _mergeInlineChildren(inline);
+      List<Widget> mergedInlines = _mergeInlineChildren(inline.children);
       final Wrap wrap = Wrap(
+        crossAxisAlignment: WrapCrossAlignment.center,
         children: mergedInlines,
       );
       _addBlockChild(wrap);
@@ -346,10 +462,10 @@
     }
   }
 
-  /// Merges adjacent [TextSpan] children of the given [_InlineElement]
-  List<Widget> _mergeInlineChildren(_InlineElement inline) {
+  /// Merges adjacent [TextSpan] children
+  List<Widget> _mergeInlineChildren(List<Widget> children) {
     List<Widget> mergedTexts = <Widget>[];
-    for (Widget child in inline.children) {
+    for (Widget child in children) {
       if (mergedTexts.isNotEmpty &&
           mergedTexts.last is RichText &&
           child is RichText) {
diff --git a/packages/flutter_markdown/lib/src/style_sheet.dart b/packages/flutter_markdown/lib/src/style_sheet.dart
index 87a48e9..70f5f88 100644
--- a/packages/flutter_markdown/lib/src/style_sheet.dart
+++ b/packages/flutter_markdown/lib/src/style_sheet.dart
@@ -22,8 +22,16 @@
     this.del,
     this.blockquote,
     this.img,
+    this.checkbox,
     this.blockSpacing,
     this.listIndent,
+    this.listBullet,
+    this.tableHead,
+    this.tableBody,
+    this.tableHeadAlign,
+    this.tableBorder,
+    this.tableCellsPadding,
+    this.tableCellsDecoration,
     this.blockquotePadding,
     this.blockquoteDecoration,
     this.codeblockPadding,
@@ -47,6 +55,10 @@
           'del': del,
           'blockquote': blockquote,
           'img': img,
+          'table': p,
+          'th': tableHead,
+          'tr': tableBody,
+          'td': tableBody,
         };
 
   /// Creates a [MarkdownStyleSheet] from the [TextStyle]s in the provided [ThemeData].
@@ -55,8 +67,8 @@
     return MarkdownStyleSheet(
       a: const TextStyle(color: Colors.blue),
       p: theme.textTheme.body1,
-      code: TextStyle(
-        color: Colors.grey.shade700,
+      code: theme.textTheme.body1.copyWith(
+        backgroundColor: Colors.grey.shade200,
         fontFamily: "monospace",
         fontSize: theme.textTheme.body1.fontSize * 0.85,
       ),
@@ -71,8 +83,18 @@
       del: const TextStyle(decoration: TextDecoration.lineThrough),
       blockquote: theme.textTheme.body1,
       img: theme.textTheme.body1,
+      checkbox: theme.textTheme.body1.copyWith(
+        color: theme.primaryColor,
+      ),
       blockSpacing: 8.0,
-      listIndent: 32.0,
+      listIndent: 24.0,
+      listBullet: theme.textTheme.body1,
+      tableHead: const TextStyle(fontWeight: FontWeight.w600),
+      tableBody: theme.textTheme.body1,
+      tableHeadAlign: TextAlign.center,
+      tableBorder: TableBorder.all(color: Colors.grey.shade300, width: 0),
+      tableCellsPadding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
+      tableCellsDecoration: BoxDecoration(color: Colors.grey.shade50),
       blockquotePadding: const EdgeInsets.all(8.0),
       blockquoteDecoration: BoxDecoration(
         color: Colors.blue.shade100,
@@ -80,7 +102,7 @@
       ),
       codeblockPadding: const EdgeInsets.all(8.0),
       codeblockDecoration: BoxDecoration(
-        color: Colors.grey.shade100,
+        color: Colors.grey.shade200,
         borderRadius: BorderRadius.circular(2.0),
       ),
       horizontalRuleDecoration: BoxDecoration(
@@ -99,8 +121,8 @@
     return MarkdownStyleSheet(
       a: const TextStyle(color: Colors.blue),
       p: theme.textTheme.body1,
-      code: TextStyle(
-        color: Colors.grey.shade700,
+      code: theme.textTheme.body1.copyWith(
+        backgroundColor: Colors.grey.shade200,
         fontFamily: "monospace",
         fontSize: theme.textTheme.body1.fontSize * 0.85,
       ),
@@ -115,8 +137,18 @@
       del: const TextStyle(decoration: TextDecoration.lineThrough),
       blockquote: theme.textTheme.body1,
       img: theme.textTheme.body1,
+      checkbox: theme.textTheme.body1.copyWith(
+        color: theme.primaryColor,
+      ),
       blockSpacing: 8.0,
-      listIndent: 32.0,
+      listIndent: 24.0,
+      listBullet: theme.textTheme.body1,
+      tableHead: const TextStyle(fontWeight: FontWeight.w600),
+      tableBody: theme.textTheme.body1,
+      tableHeadAlign: TextAlign.center,
+      tableBorder: TableBorder.all(color: Colors.grey.shade300),
+      tableCellsPadding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
+      tableCellsDecoration: BoxDecoration(color: Colors.grey.shade50),
       blockquotePadding: const EdgeInsets.all(8.0),
       blockquoteDecoration: BoxDecoration(
         color: Colors.blue.shade100,
@@ -124,7 +156,7 @@
       ),
       codeblockPadding: const EdgeInsets.all(8.0),
       codeblockDecoration: BoxDecoration(
-        color: Colors.grey.shade100,
+        color: Colors.grey.shade200,
         borderRadius: BorderRadius.circular(2.0),
       ),
       horizontalRuleDecoration: BoxDecoration(
@@ -152,8 +184,16 @@
     TextStyle del,
     TextStyle blockquote,
     TextStyle img,
+    TextStyle checkbox,
     double blockSpacing,
     double listIndent,
+    TextStyle listBullet,
+    TextStyle tableHead,
+    TextStyle tableBody,
+    TextAlign tableHeadAlign,
+    TableBorder tableBorder,
+    EdgeInsets tableCellsPadding,
+    Decoration tableCellsDecoration,
     EdgeInsets blockquotePadding,
     Decoration blockquoteDecoration,
     EdgeInsets codeblockPadding,
@@ -176,8 +216,16 @@
       del: del ?? this.del,
       blockquote: blockquote ?? this.blockquote,
       img: img ?? this.img,
+      checkbox: checkbox ?? this.checkbox,
       blockSpacing: blockSpacing ?? this.blockSpacing,
       listIndent: listIndent ?? this.listIndent,
+      listBullet: listBullet ?? this.listBullet,
+      tableHead: tableHead ?? this.tableHead,
+      tableBody: tableBody ?? this.tableBody,
+      tableHeadAlign: tableHeadAlign ?? this.tableHeadAlign,
+      tableBorder: tableBorder ?? this.tableBorder,
+      tableCellsPadding: tableCellsPadding ?? this.tableCellsPadding,
+      tableCellsDecoration: tableCellsDecoration ?? this.tableCellsDecoration,
       blockquotePadding: blockquotePadding ?? this.blockquotePadding,
       blockquoteDecoration: blockquoteDecoration ?? this.blockquoteDecoration,
       codeblockPadding: codeblockPadding ?? this.codeblockPadding,
@@ -230,12 +278,36 @@
   /// The [TextStyle] to use for `img` elements.
   final TextStyle img;
 
+  /// The [TextStyle] to use for `input` elements.
+  final TextStyle checkbox;
+
   /// The amount of vertical space to use between block-level elements.
   final double blockSpacing;
 
   /// The amount of horizontal space to indent list items.
   final double listIndent;
 
+  /// The [TextStyle] to use for bullets.
+  final TextStyle listBullet;
+
+  /// The [TextStyle] to use for `th` elements.
+  final TextStyle tableHead;
+
+  /// The [TextStyle] to use for `td` elements.
+  final TextStyle tableBody;
+
+  /// The [TextAlign] to use for `th` elements.
+  final TextAlign tableHeadAlign;
+
+  /// The [TableBorder] to use for `table` elements.
+  final TableBorder tableBorder;
+
+  /// The padding to use for `th` and `td` elements.
+  final EdgeInsets tableCellsPadding;
+
+  /// The decoration to use for `th` and `td` elements.
+  final Decoration tableCellsDecoration;
+
   /// The padding to use for `blockquote` elements.
   final EdgeInsets blockquotePadding;
 
@@ -277,8 +349,16 @@
         typedOther.del == del &&
         typedOther.blockquote == blockquote &&
         typedOther.img == img &&
+        typedOther.checkbox == checkbox &&
         typedOther.blockSpacing == blockSpacing &&
         typedOther.listIndent == listIndent &&
+        typedOther.listBullet == listBullet &&
+        typedOther.tableHead == tableHead &&
+        typedOther.tableBody == tableBody &&
+        typedOther.tableHeadAlign == tableHeadAlign &&
+        typedOther.tableBorder == tableBorder &&
+        typedOther.tableCellsPadding == tableCellsPadding &&
+        typedOther.tableCellsDecoration == tableCellsDecoration &&
         typedOther.blockquotePadding == blockquotePadding &&
         typedOther.blockquoteDecoration == blockquoteDecoration &&
         typedOther.codeblockPadding == codeblockPadding &&
@@ -304,8 +384,16 @@
       del,
       blockquote,
       img,
+      checkbox,
       blockSpacing,
       listIndent,
+      listBullet,
+      tableHead,
+      tableBody,
+      tableHeadAlign,
+      tableBorder,
+      tableCellsPadding,
+      tableCellsDecoration,
       blockquotePadding,
       blockquoteDecoration,
       codeblockPadding,
diff --git a/packages/flutter_markdown/lib/src/widget.dart b/packages/flutter_markdown/lib/src/widget.dart
index e7ceb11..e13e2e1 100644
--- a/packages/flutter_markdown/lib/src/widget.dart
+++ b/packages/flutter_markdown/lib/src/widget.dart
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:io';
-
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:markdown/markdown.dart' as md;
@@ -17,6 +15,16 @@
 /// Used by [MarkdownWidget.onTapLink].
 typedef void MarkdownTapLinkCallback(String href);
 
+/// Signature for custom image widget.
+///
+/// Used by [MarkdownWidget.imageBuilder]
+typedef Widget MarkdownImageBuilder(Uri uri);
+
+/// Signature for custom checkbox widget.
+///
+/// Used by [MarkdownWidget.checkboxBuilder]
+typedef Widget MarkdownCheckboxBuilder(bool value);
+
 /// Creates a format [TextSpan] given a string.
 ///
 /// Used by [MarkdownWidget] to highlight the contents of `pre` elements.
@@ -29,13 +37,13 @@
 /// A base class for widgets that parse and display Markdown.
 ///
 /// Supports all standard Markdown from the original
-/// [Markdown specification](https://daringfireball.net/projects/markdown/).
+/// [Markdown specification](https://github.github.com/gfm/).
 ///
 /// See also:
 ///
 ///  * [Markdown], which is a scrolling container of Markdown.
 ///  * [MarkdownBody], which is a non-scrolling container of Markdown.
-///  * <https://daringfireball.net/projects/markdown/>
+///  * <https://github.github.com/gfm/>
 abstract class MarkdownWidget extends StatefulWidget {
   /// Creates a widget that parses and displays Markdown.
   ///
@@ -47,6 +55,9 @@
     this.syntaxHighlighter,
     this.onTapLink,
     this.imageDirectory,
+    this.extensionSet,
+    this.imageBuilder,
+    this.checkboxBuilder,
   })  : assert(data != null),
         super(key: key);
 
@@ -66,8 +77,19 @@
   /// Called when the user taps a link.
   final MarkdownTapLinkCallback onTapLink;
 
-  /// The base directory holding images referenced by Img tags with local file paths.
-  final Directory imageDirectory;
+  /// The base directory holding images referenced by Img tags with local or network file paths.
+  final String imageDirectory;
+
+  /// Markdown syntax extension set
+  ///
+  /// Defaults to [md.ExtensionSet.gitHubFlavored]
+  final md.ExtensionSet extensionSet;
+
+  /// Call when build an image widget.
+  final MarkdownImageBuilder imageBuilder;
+
+  /// Call when build a checkbox widget.
+  final MarkdownCheckboxBuilder checkboxBuilder;
 
   /// Subclasses should override this function to display the given children,
   /// which are the parsed representation of [data].
@@ -110,7 +132,8 @@
 
     final List<String> lines = widget.data.split(RegExp(r'\r?\n'));
     final md.Document document = md.Document(
-      inlineSyntaxes: [md.AutolinkExtensionSyntax(), md.StrikethroughSyntax()],
+      extensionSet: widget.extensionSet ?? md.ExtensionSet.gitHubFlavored,
+      inlineSyntaxes: [TaskListSyntax()],
       encodeHtml: false,
     );
     final MarkdownBuilder builder = MarkdownBuilder(
@@ -141,6 +164,7 @@
 
   @override
   TextSpan formatText(MarkdownStyleSheet styleSheet, String code) {
+    code = code.replaceAll(RegExp(r'\n$'), '');
     if (widget.syntaxHighlighter != null) {
       return widget.syntaxHighlighter.format(code);
     }
@@ -153,13 +177,13 @@
 
 /// A non-scrolling widget that parses and displays Markdown.
 ///
-/// Supports all standard Markdown from the original
-/// [Markdown specification](https://daringfireball.net/projects/markdown/).
+/// Supports all GitHub Flavored Markdown from the
+/// [specification](https://github.github.com/gfm/).
 ///
 /// See also:
 ///
 ///  * [Markdown], which is a scrolling container of Markdown.
-///  * <https://daringfireball.net/projects/markdown/>
+///  * <https://github.github.com/gfm/>
 class MarkdownBody extends MarkdownWidget {
   /// Creates a non-scrolling widget that parses and displays Markdown.
   const MarkdownBody({
@@ -168,7 +192,11 @@
     MarkdownStyleSheet styleSheet,
     SyntaxHighlighter syntaxHighlighter,
     MarkdownTapLinkCallback onTapLink,
-    Directory imageDirectory,
+    String imageDirectory,
+    md.ExtensionSet extensionSet,
+    MarkdownImageBuilder imageBuilder,
+    MarkdownCheckboxBuilder checkboxBuilder,
+    this.shrinkWrap = false,
   }) : super(
           key: key,
           data: data,
@@ -176,12 +204,19 @@
           syntaxHighlighter: syntaxHighlighter,
           onTapLink: onTapLink,
           imageDirectory: imageDirectory,
+          extensionSet: extensionSet,
+          imageBuilder: imageBuilder,
+          checkboxBuilder: checkboxBuilder,
         );
 
+  /// See [ScrollView.shrinkWrap]
+  final bool shrinkWrap;
+
   @override
   Widget build(BuildContext context, List<Widget> children) {
-    if (children.length == 1) return children.single;
+    if (children.length == 1 && !shrinkWrap) return children.single;
     return Column(
+      mainAxisSize: shrinkWrap ? MainAxisSize.min : MainAxisSize.max,
       crossAxisAlignment: CrossAxisAlignment.stretch,
       children: children,
     );
@@ -190,13 +225,13 @@
 
 /// A scrolling widget that parses and displays Markdown.
 ///
-/// Supports all standard Markdown from the original
-/// [Markdown specification](https://daringfireball.net/projects/markdown/).
+/// Supports all GitHub Flavored Markdown from the
+/// [specification](https://github.github.com/gfm/).
 ///
 /// See also:
 ///
 ///  * [MarkdownBody], which is a non-scrolling container of Markdown.
-///  * <https://daringfireball.net/projects/markdown/>
+///  * <https://github.github.com/gfm/>
 class Markdown extends MarkdownWidget {
   /// Creates a scrolling widget that parses and displays Markdown.
   const Markdown({
@@ -205,8 +240,13 @@
     MarkdownStyleSheet styleSheet,
     SyntaxHighlighter syntaxHighlighter,
     MarkdownTapLinkCallback onTapLink,
-    Directory imageDirectory,
+    String imageDirectory,
+    md.ExtensionSet extensionSet,
+    MarkdownImageBuilder imageBuilder,
+    MarkdownCheckboxBuilder checkboxBuilder,
     this.padding: const EdgeInsets.all(16.0),
+    this.physics,
+    this.shrinkWrap: false,
   }) : super(
           key: key,
           data: data,
@@ -214,13 +254,50 @@
           syntaxHighlighter: syntaxHighlighter,
           onTapLink: onTapLink,
           imageDirectory: imageDirectory,
+          extensionSet: extensionSet,
+          imageBuilder: imageBuilder,
+          checkboxBuilder: checkboxBuilder,
         );
 
   /// The amount of space by which to inset the children.
   final EdgeInsets padding;
 
+  /// How the scroll view should respond to user input.
+  ///
+  /// See also: [ScrollView.physics]
+  final ScrollPhysics physics;
+
+  /// Whether the extent of the scroll view in the scroll direction should be
+  /// determined by the contents being viewed.
+  ///
+  /// See also: [ScrollView.shrinkWrap]
+  final bool shrinkWrap;
+
   @override
   Widget build(BuildContext context, List<Widget> children) {
-    return ListView(padding: padding, children: children);
+    return ListView(
+      padding: padding,
+      physics: physics,
+      shrinkWrap: shrinkWrap,
+      children: children,
+    );
+  }
+}
+
+/// Parse [task list items](https://github.github.com/gfm/#task-list-items-extension-).
+class TaskListSyntax extends md.InlineSyntax {
+  // FIXME: incorrect
+  static final String _pattern = r'^ *\[([ xX])\] +';
+
+  TaskListSyntax() : super(_pattern);
+
+  @override
+  bool onMatch(md.InlineParser parser, Match match) {
+    md.Element el = md.Element.withTag('input');
+    el.attributes['type'] = 'checkbox';
+    el.attributes['disabled'] = 'true';
+    el.attributes['checked'] = '${match[1].trim().isNotEmpty}';
+    parser.addNode(el);
+    return true;
   }
 }
diff --git a/packages/flutter_markdown/pubspec.lock b/packages/flutter_markdown/pubspec.lock
index ddf7393..7758c46 100644
--- a/packages/flutter_markdown/pubspec.lock
+++ b/packages/flutter_markdown/pubspec.lock
@@ -173,7 +173,7 @@
     source: hosted
     version: "0.12.5"
   meta:
-    dependency: "direct main"
+    dependency: transitive
     description:
       name: meta
       url: "https://pub.flutter-io.cn"
@@ -222,7 +222,7 @@
     source: hosted
     version: "1.0.10"
   path:
-    dependency: "direct main"
+    dependency: transitive
     description:
       name: path
       url: "https://pub.flutter-io.cn"
@@ -332,7 +332,7 @@
     source: hosted
     version: "2.0.0"
   string_scanner:
-    dependency: "direct main"
+    dependency: transitive
     description:
       name: string_scanner
       url: "https://pub.flutter-io.cn"
diff --git a/packages/flutter_markdown/pubspec.yaml b/packages/flutter_markdown/pubspec.yaml
index 8509a73..4f1e7fb 100644
--- a/packages/flutter_markdown/pubspec.yaml
+++ b/packages/flutter_markdown/pubspec.yaml
@@ -8,9 +8,6 @@
   flutter:
     sdk: flutter
   markdown: ^2.0.0
-  meta: ^1.0.5
-  string_scanner: ^1.0.0
-  path: ^1.5.1
 
 dev_dependencies:
   flutter_test:
diff --git a/packages/flutter_markdown/test/flutter_markdown_test.dart b/packages/flutter_markdown/test/flutter_markdown_test.dart
index e33573e..ee007e5 100644
--- a/packages/flutter_markdown/test/flutter_markdown_test.dart
+++ b/packages/flutter_markdown/test/flutter_markdown_test.dart
@@ -44,6 +44,15 @@
     _expectTextStrings(widgets, <String>['strikethrough']);
   });
 
+  testWidgets('Line break', (WidgetTester tester) async {
+    await tester.pumpWidget(_boilerplate(const MarkdownBody(data: 'line 1  \nline 2')));
+
+    final Iterable<Widget> widgets = tester.allWidgets;
+    _expectWidgetTypes(
+        widgets, <Type>[Directionality, MarkdownBody, Column, Wrap, RichText]);
+    _expectTextStrings(widgets, <String>['line 1\nline 2']);
+  });
+
   testWidgets('Empty string', (WidgetTester tester) async {
     await tester.pumpWidget(_boilerplate(const MarkdownBody(data: '')));
 
@@ -83,6 +92,18 @@
     ]);
   });
 
+  testWidgets('Task list', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      _boilerplate(const MarkdownBody(data: '- [x] Item 1\n- [ ] Item 2')),
+    );
+
+    final Iterable<Widget> widgets = tester.allWidgets;
+
+    // \ue834 -> Icons.check_box
+    // \ue835 -> Icons.check_box_outline_blank
+    _expectTextStrings(widgets, <String>['\ue834', 'Item 1', '\ue835', 'Item 2']);
+  });
+
   testWidgets('Horizontal Rule', (WidgetTester tester) async {
     await tester.pumpWidget(_boilerplate(const MarkdownBody(data: '-----')));
 
@@ -348,6 +369,59 @@
     });
   });
 
+  group('Tables', () {
+    testWidgets('should show properly', (WidgetTester tester) async {
+      const String data = '|Header 1|Header 2|\n|-----|-----|\n|Col 1|Col 2|';
+      await tester.pumpWidget(_boilerplate(const MarkdownBody(data: data)));
+
+      final Iterable<Widget> widgets = tester.allWidgets;
+      _expectTextStrings(
+          widgets, <String>['Header 1', 'Header 2', 'Col 1', 'Col 2']);
+    });
+
+    testWidgets('work without the outer pipes', (WidgetTester tester) async {
+      const String data = 'Header 1|Header 2\n-----|-----\nCol 1|Col 2';
+      await tester.pumpWidget(_boilerplate(const MarkdownBody(data: data)));
+
+      final Iterable<Widget> widgets = tester.allWidgets;
+      _expectTextStrings(
+          widgets, <String>['Header 1', 'Header 2', 'Col 1', 'Col 2']);
+    });
+
+    testWidgets('should work with alignments', (WidgetTester tester) async {
+      const String data = '|Header 1|Header 2|\n|:----:|----:|\n|Col 1|Col 2|';
+      await tester.pumpWidget(_boilerplate(const MarkdownBody(data: data)));
+
+      final Iterable<Widget> widgets = tester.allWidgets;
+      final DefaultTextStyle style = widgets.firstWhere((Widget widget) => widget is DefaultTextStyle);
+      final DefaultTextStyle style2 = widgets.lastWhere((Widget widget) => widget is DefaultTextStyle);
+
+      expect(style.textAlign, TextAlign.center);
+      expect(style2.textAlign, TextAlign.right);
+    });
+
+    testWidgets('should work with styling', (WidgetTester tester) async {
+      const String data = '|Header|\n|----|\n|*italic*|';
+      await tester.pumpWidget(_boilerplate(MarkdownBody(data: data)));
+
+      final Iterable<Widget> widgets = tester.allWidgets;
+      final RichText richText = widgets.lastWhere((Widget widget) => widget is RichText);
+
+      _expectTextStrings(widgets, <String>['Header', 'italic']);
+      expect(richText.text.style.fontStyle, FontStyle.italic);
+    });
+
+    testWidgets('should work next to other tables', (WidgetTester tester) async {
+      const String data = '|first header|\n|----|\n|first col|\n\n'
+          '|second header|\n|----|\n|second col|';
+      await tester.pumpWidget(_boilerplate(const MarkdownBody(data: data)));
+
+      final Iterable<Widget> tables = tester.allWidgets.where((Widget widget) => widget is Table);
+
+      expect(tables.length, 2);
+    });
+  });
+
   group('uri data scheme', () {
     testWidgets('should work with image in uri data scheme', (WidgetTester tester) async {
       const String imageData = '![alt](data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=)';