Adds initial version of Markdown renderer
diff --git a/packages/flutter_markdown/README.md b/packages/flutter_markdown/README.md
new file mode 100644
index 0000000..db83e6b
--- /dev/null
+++ b/packages/flutter_markdown/README.md
@@ -0,0 +1,22 @@
+# Flutter Markdown
+
+A markdown renderer for Flutter. It supports the
+[original format](https://daringfireball.net/projects/markdown/), but no inline
+html.
+
+## Getting Started
+
+Using the Markdown widget is simple, just pass in the source markdown as a
+string:
+
+    new Markdown(data: markdownSource);
+
+If you do not want the padding or scrolling behavior, use the MarkdownBody
+instead:
+
+    new MarkdownBody(data: markdownSource);
+
+By default, Markdown uses the formatting from the current material design theme,
+but it's possible to create your own custom styling. Use the MarkdownStyle class
+to pass in your own style. If you don't want to use Markdown outside of material
+design, use the MarkdownRaw class.
diff --git a/packages/flutter_markdown/example/demo.dart b/packages/flutter_markdown/example/demo.dart
new file mode 100644
index 0000000..14bce27
--- /dev/null
+++ b/packages/flutter_markdown/example/demo.dart
@@ -0,0 +1,36 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_markdown/flutter_markdown.dart';
+
+const String _kMarkdownData = """# Markdown Example
+Markdown allows you to easily include formatted text, images, and even formatted Dart code in your app.
+
+## Styling
+Style text as _italic_, __bold__, or `inline code`.
+
+- Use bulleted lists
+- To better clarify
+- Your points
+
+## Code blocks
+Formatted Dart code looks really pretty too. This is an example of how to create your own Markdown widget:
+
+    new Markdown(data: "Hello _world_!");
+
+Enjoy!
+""";
+
+void main() {
+  runApp(new MaterialApp(
+    title: "Markdown Demo",
+    routes: <String, RouteBuilder>{
+      '/': (RouteArguments args) => new Scaffold(
+        toolBar: new ToolBar(center: new Text("Markdown Demo")),
+        body: new Markdown(data: _kMarkdownData)
+      )
+    }
+  ));
+}
diff --git a/packages/flutter_markdown/lib/flutter_markdown.dart b/packages/flutter_markdown/lib/flutter_markdown.dart
new file mode 100644
index 0000000..736e84f
--- /dev/null
+++ b/packages/flutter_markdown/lib/flutter_markdown.dart
@@ -0,0 +1,8 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library flutter_markdown;
+
+export 'src/markdown.dart';
+export 'src/markdown_style.dart';
diff --git a/packages/flutter_markdown/lib/flutter_markdown_raw.dart b/packages/flutter_markdown/lib/flutter_markdown_raw.dart
new file mode 100644
index 0000000..d7fed2c
--- /dev/null
+++ b/packages/flutter_markdown/lib/flutter_markdown_raw.dart
@@ -0,0 +1,8 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library flutter_markdown;
+
+export 'src/markdown_raw.dart';
+export 'src/markdown_style_raw.dart';
diff --git a/packages/flutter_markdown/lib/src/markdown.dart b/packages/flutter_markdown/lib/src/markdown.dart
new file mode 100644
index 0000000..3b437b1
--- /dev/null
+++ b/packages/flutter_markdown/lib/src/markdown.dart
@@ -0,0 +1,84 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+
+import 'markdown_raw.dart';
+import 'markdown_style.dart';
+
+/// A [Widget] that renders markdown formatted text. It supports all standard
+/// markdowns from the original markdown specification found here:
+/// https://daringfireball.net/projects/markdown/ The rendered markdown is
+/// placed in a padded scrolling view port. If you do not want the scrolling
+/// behaviour, use the [MarkdownBody] class instead.
+class Markdown extends MarkdownRaw {
+
+  /// Creates a new Markdown [Widget] that renders the markdown formatted string
+  /// passed in as [data]. By default the markdown will be rendered using the
+  /// styles from the current theme, but you can optionally pass in a custom
+  /// [markdownStyle] that specifies colors and fonts to use. Code blocks are
+  /// by default not using syntax highlighting, but it's possible to pass in
+  /// a custom [syntaxHighlighter].
+  ///
+  ///     new Markdown(data: "Hello _world_!");
+  Markdown({
+    String data,
+    SyntaxHighlighter syntaxHighlighter,
+    MarkdownStyle markdownStyle
+  }) : super(
+    data: data,
+    syntaxHighlighter: syntaxHighlighter,
+    markdownStyle: markdownStyle
+  );
+
+  MarkdownBody createMarkdownBody({
+    String data,
+    MarkdownStyle markdownStyle,
+    SyntaxHighlighter syntaxHighlighter
+  }) {
+    return new MarkdownBody(
+      data: data,
+      markdownStyle: markdownStyle,
+      syntaxHighlighter: syntaxHighlighter
+    );
+  }
+}
+
+/// A [Widget] that renders markdown formatted text. It supports all standard
+/// markdowns from the original markdown specification found here:
+/// https://daringfireball.net/projects/markdown/ This class doesn't implement
+/// any scrolling behavior, if you want scrolling either wrap the widget in
+/// a [ScrollableViewport] or use the [Markdown] widget.
+class MarkdownBody extends MarkdownBodyRaw {
+
+  /// Creates a new Markdown [Widget] that renders the markdown formatted string
+  /// passed in as [data]. By default the markdown will be rendered using the
+  /// styles from the current theme, but you can optionally pass in a custom
+  /// [markdownStyle] that specifies colors and fonts to use. Code blocks are
+  /// by default not using syntax highlighting, but it's possible to pass in
+  /// a custom [syntaxHighlighter].
+  ///
+  /// Typically, you may want to wrap the [MarkdownBody] widget in a [Padding] and
+  /// a [ScrollableViewport], or use the [Markdown] class
+  ///
+  ///     new ScrollableViewport(
+  ///       child: new Padding(
+  ///         padding: new EdgeDims.all(16.0),
+  ///         child: new Markdown(data: markdownSource)
+  ///       )
+  ///     )
+  MarkdownBody({
+    String data,
+    SyntaxHighlighter syntaxHighlighter,
+    MarkdownStyle markdownStyle
+  }) : super(
+    data: data,
+    syntaxHighlighter: syntaxHighlighter,
+    markdownStyle: markdownStyle
+  );
+
+  MarkdownStyle createDefaultStyle(BuildContext context) {
+    return new MarkdownStyle.defaultFromTheme(Theme.of(context));
+  }
+}
diff --git a/packages/flutter_markdown/lib/src/markdown_raw.dart b/packages/flutter_markdown/lib/src/markdown_raw.dart
new file mode 100644
index 0000000..043e3e1
--- /dev/null
+++ b/packages/flutter_markdown/lib/src/markdown_raw.dart
@@ -0,0 +1,433 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:markdown/markdown.dart' as md;
+import 'package:flutter/widgets.dart';
+import 'markdown_style_raw.dart';
+
+
+/// A [Widget] that renders markdown formatted text. It supports all standard
+/// markdowns from the original markdown specification found here:
+/// https://daringfireball.net/projects/markdown/ The rendered markdown is
+/// placed in a padded scrolling view port. If you do not want the scrolling
+/// behaviour, use the [MarkdownBodyRaw] class instead.
+class MarkdownRaw extends StatelessComponent {
+
+  /// Creates a new Markdown [Widget] that renders the markdown formatted string
+  /// passed in as [data]. By default the markdown will be rendered using the
+  /// styles from the current theme, but you can optionally pass in a custom
+  /// [markdownStyle] that specifies colors and fonts to use. Code blocks are
+  /// by default not using syntax highlighting, but it's possible to pass in
+  /// a custom [syntaxHighlighter].
+  ///
+  ///     new MarkdownRaw(data: "Hello _world_!", markdownStyle: myStyle);
+  MarkdownRaw({
+    this.data,
+    this.markdownStyle,
+    this.syntaxHighlighter,
+    this.padding: const EdgeDims.all(16.0)
+  });
+
+  /// Markdown styled text
+  final String data;
+
+  /// Style used for rendering the markdown
+  final MarkdownStyleRaw markdownStyle;
+
+  /// The syntax highlighter used to color text in code blocks
+  final SyntaxHighlighter syntaxHighlighter;
+
+  /// Padding used
+  final EdgeDims padding;
+
+  Widget build(BuildContext context) {
+    return new ScrollableViewport(
+      child: new Padding(
+        padding: padding,
+        child: createMarkdownBody(
+          data: data,
+          markdownStyle: markdownStyle,
+          syntaxHighlighter: syntaxHighlighter
+        )
+      )
+    );
+  }
+
+  MarkdownBodyRaw createMarkdownBody({
+    String data,
+    MarkdownStyleRaw markdownStyle,
+    SyntaxHighlighter syntaxHighlighter
+  }) {
+    return new MarkdownBodyRaw(
+      data: data,
+      markdownStyle: markdownStyle,
+      syntaxHighlighter: syntaxHighlighter
+    );
+  }
+}
+
+/// A [Widget] that renders markdown formatted text. It supports all standard
+/// markdowns from the original markdown specification found here:
+/// https://daringfireball.net/projects/markdown/ This class doesn't implement
+/// any scrolling behavior, if you want scrolling either wrap the widget in
+/// a [ScrollableViewport] or use the [MarkdownRaw] widget.
+class MarkdownBodyRaw extends StatefulComponent {
+
+  /// Creates a new Markdown [Widget] that renders the markdown formatted string
+  /// passed in as [data]. You need to pass in a [markdownStyle] that defines
+  /// how the code is rendered. Code blocks are by default not using syntax
+  /// highlighting, but it's possible to pass in a custom [syntaxHighlighter].
+  ///
+  /// Typically, you may want to wrap the [MarkdownBodyRaw] widget in a
+  /// [Padding] and a [ScrollableViewport], or use the [Markdown class]
+  ///
+  ///     new ScrollableViewport(
+  ///       child: new Padding(
+  ///         padding: new EdgeDims.all(16.0),
+  ///         child: new MarkdownBodyRaw(
+  ///           data: markdownSource,
+  ///           markdownStyle: myStyle
+  ///         )
+  ///       )
+  ///     )
+  MarkdownBodyRaw({
+    this.data,
+    this.markdownStyle,
+    this.syntaxHighlighter
+  });
+
+  /// Markdown styled text
+  final String data;
+
+  /// Style used for rendering the markdown
+  final MarkdownStyleRaw markdownStyle;
+
+  /// The syntax highlighter used to color text in code blocks
+  final SyntaxHighlighter syntaxHighlighter;
+
+  _MarkdownBodyRawState createState() => new _MarkdownBodyRawState();
+
+  MarkdownStyleRaw createDefaultStyle(BuildContext context) => null;
+}
+
+class _MarkdownBodyRawState extends State<MarkdownBodyRaw> {
+
+  void initState() {
+    super.initState();
+
+    MarkdownStyleRaw markdownStyle = config.markdownStyle ?? config.createDefaultStyle(context);
+    SyntaxHighlighter syntaxHighlighter = config.syntaxHighlighter ?? new _DefaultSyntaxHighlighter(markdownStyle.code);
+
+    _cachedBlocks = _blocksFromMarkup(config.data, markdownStyle, syntaxHighlighter);
+  }
+
+  List<_Block> _cachedBlocks;
+
+  Widget build(BuildContext context) {
+    List<Widget> blocks = <Widget>[];
+    for (_Block block in _cachedBlocks) {
+      blocks.add(block.build(context));
+    }
+
+    return new Column(
+      alignItems: FlexAlignItems.stretch,
+      children: blocks
+    );
+  }
+}
+
+List<_Block> _blocksFromMarkup(String data, MarkdownStyleRaw markdownStyle, SyntaxHighlighter syntaxHighlighter) {
+  // TODO: This can be optimized by doing the split and removing \r at the same time
+  List<String> lines = data.replaceAll('\r\n', '\n').split('\n');
+  md.Document document = new md.Document();
+
+  _Renderer renderer = new _Renderer();
+  return renderer.render(document.parseLines(lines), markdownStyle, syntaxHighlighter);
+}
+
+class _Renderer implements md.NodeVisitor {
+  List<_Block> render(List<md.Node> nodes, MarkdownStyleRaw markdownStyle, SyntaxHighlighter syntaxHighlighter) {
+    assert(markdownStyle != null);
+
+    _blocks = <_Block>[];
+    _listIndents = <String>[];
+    _markdownStyle = markdownStyle;
+    _syntaxHighlighter = syntaxHighlighter;
+
+    for (final md.Node node in nodes) {
+      node.accept(this);
+    }
+
+    return _blocks;
+  }
+
+  List<_Block> _blocks;
+  List<String> _listIndents;
+  MarkdownStyleRaw _markdownStyle;
+  SyntaxHighlighter _syntaxHighlighter;
+
+  void visitText(md.Text text) {
+    _MarkdownNodeList topList = _currentBlock.stack.last;
+    List<_MarkdownNode> top = topList.list;
+
+    if (_currentBlock.tag == 'pre')
+      top.add(new _MarkdownNodeTextSpan(_syntaxHighlighter.format(text.text)));
+    else
+      top.add(new _MarkdownNodeString(text.text));
+  }
+
+  bool visitElementBefore(md.Element element) {
+    if (_isListTag(element.tag))
+      _listIndents.add(element.tag);
+
+    if (_isBlockTag(element.tag)) {
+      List<_Block> blockList;
+      if (_currentBlock == null)
+        blockList = _blocks;
+      else
+        blockList = _currentBlock.subBlocks;
+
+      _Block newBlock = new _Block(element.tag, element.attributes, _markdownStyle, new List<String>.from(_listIndents), blockList.length);
+      blockList.add(newBlock);
+    } else {
+      TextStyle style = _markdownStyle.styles[element.tag] ?? new TextStyle();
+      List<_MarkdownNode> styleElement = <_MarkdownNode>[new _MarkdownNodeTextStyle(style)];
+      _currentBlock.stack.add(new _MarkdownNodeList(styleElement));
+    }
+    return true;
+  }
+
+  void visitElementAfter(md.Element element) {
+    if (_isListTag(element.tag))
+      _listIndents.removeLast();
+
+    if (_isBlockTag(element.tag)) {
+      if (_currentBlock.stack.length > 0) {
+        _MarkdownNodeList stackList = _currentBlock.stack.first;
+        _currentBlock.stack = stackList.list;
+        _currentBlock.open = false;
+      } else {
+        _currentBlock.stack = <_MarkdownNode>[new _MarkdownNodeString('')];
+      }
+    } else {
+      if (_currentBlock.stack.length > 1) {
+        _MarkdownNodeList poppedList = _currentBlock.stack.last;
+        List<_MarkdownNode> popped = poppedList.list;
+        _currentBlock.stack.removeLast();
+
+        _MarkdownNodeList topList = _currentBlock.stack.last;
+        List<_MarkdownNode> top = topList.list;
+        top.add(new _MarkdownNodeList(popped));
+      }
+    }
+  }
+
+  static const List<String> _kBlockTags = const <String>['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'blockquote', 'img', 'pre', 'ol', 'ul'];
+  static const List<String> _kListTags = const <String>['ul', 'ol'];
+
+  bool _isBlockTag(String tag) {
+    return _kBlockTags.contains(tag);
+  }
+
+  bool _isListTag(String tag) {
+    return _kListTags.contains(tag);
+  }
+
+  _Block get _currentBlock => _currentBlockInList(_blocks);
+
+  _Block _currentBlockInList(List<_Block> blocks) {
+    if (blocks.isEmpty)
+      return null;
+
+    if (!blocks.last.open)
+      return null;
+
+    _Block childBlock = _currentBlockInList(blocks.last.subBlocks);
+    if (childBlock != null)
+      return childBlock;
+
+    return blocks.last;
+  }
+}
+
+abstract class _MarkdownNode {
+}
+
+class _MarkdownNodeList extends _MarkdownNode {
+  _MarkdownNodeList(this.list);
+  List<_MarkdownNode> list;
+}
+
+class _MarkdownNodeTextStyle extends _MarkdownNode {
+  _MarkdownNodeTextStyle(this.style);
+  TextStyle style;
+}
+
+class _MarkdownNodeString extends _MarkdownNode {
+  _MarkdownNodeString(this.string);
+  String string;
+}
+
+class _MarkdownNodeTextSpan extends _MarkdownNode {
+  _MarkdownNodeTextSpan(this.textSpan);
+  TextSpan textSpan;
+}
+
+class _Block {
+  _Block(this.tag, this.attributes, this.markdownStyle, this.listIndents, this.blockPosition) {
+    TextStyle style = markdownStyle.styles[tag];
+    if (style == null)
+      style = new TextStyle(color: const Color(0xffff0000));
+
+    stack = <_MarkdownNode>[new _MarkdownNodeList(<_MarkdownNode>[new _MarkdownNodeTextStyle(style)])];
+    subBlocks = <_Block>[];
+  }
+
+  final String tag;
+  final Map<String, String> attributes;
+  final MarkdownStyleRaw markdownStyle;
+  final List<String> listIndents;
+  final int blockPosition;
+
+  List<_MarkdownNode> stack;
+  List<_Block> subBlocks;
+
+  bool get open => _open;
+  void set open(bool open) {
+    _open = open;
+    if (!open && subBlocks.length > 0)
+      subBlocks.last.isLast = true;
+  }
+
+  bool _open = true;
+  bool isLast = false;
+
+  Widget build(BuildContext context) {
+
+    if (tag == 'img') {
+      return _buildImage(context, attributes['src']);
+    }
+
+    double spacing = markdownStyle.blockSpacing;
+    if (isLast) spacing = 0.0;
+
+    Widget contents;
+
+    if (subBlocks.length > 0) {
+      List<Widget> subWidgets = <Widget>[];
+      for (_Block subBlock in subBlocks) {
+        subWidgets.add(subBlock.build(context));
+      }
+
+      contents = new Column(
+        alignItems: FlexAlignItems.stretch,
+        children: subWidgets
+      );
+    } else {
+      contents = new RichText(text: _stackToTextSpan(new _MarkdownNodeList(stack)));
+
+      if (listIndents.length > 0) {
+        Widget bullet;
+        if (listIndents.last == 'ul') {
+          bullet = new Text(
+            '•',
+            style: new TextStyle(textAlign: TextAlign.center)
+          );
+        }
+        else {
+          bullet = new Padding(
+            padding: new EdgeDims.only(right: 5.0),
+            child: new Text(
+              "${blockPosition + 1}.",
+              style: new TextStyle(textAlign: TextAlign.right)
+            )
+          );
+        }
+
+        contents = new Row(
+          alignItems: FlexAlignItems.start,
+          children: <Widget>[
+            new SizedBox(
+              width: listIndents.length * markdownStyle.listIndent,
+              child: bullet
+            ),
+            new Flexible(child: contents)
+          ]
+        );
+      }
+    }
+
+    BoxDecoration decoration;
+    EdgeDims padding;
+
+    if (tag == 'blockquote') {
+      decoration = markdownStyle.blockquoteDecoration;
+      padding = new EdgeDims.all(markdownStyle.blockquotePadding);
+    } else if (tag == 'pre') {
+      decoration = markdownStyle.codeblockDecoration;
+      padding = new EdgeDims.all(markdownStyle.codeblockPadding);
+    }
+
+    return new Container(
+      padding: padding,
+      margin: new EdgeDims.only(bottom: spacing),
+      child: contents,
+      decoration: decoration
+    );
+  }
+
+  TextSpan _stackToTextSpan(_MarkdownNode stack) {
+    if (stack is _MarkdownNodeTextSpan)
+      return stack.textSpan;
+
+    if (stack is _MarkdownNodeList) {
+      List<_MarkdownNode> list = stack.list;
+      _MarkdownNodeTextStyle styleNode = list[0];
+      TextStyle style = styleNode.style;
+
+      List<TextSpan> children = <TextSpan>[];
+      for (int i = 1; i < list.length; i++) {
+        children.add(_stackToTextSpan(list[i]));
+      }
+      return new TextSpan(style: style, children: children);
+    }
+
+    if (stack is _MarkdownNodeString) {
+      return new TextSpan(text: stack.string);
+    }
+
+    return null;
+  }
+
+  Widget _buildImage(BuildContext context, String src) {
+    List<String> parts = src.split('#');
+    if (parts.length == 0) return new Container();
+
+    String path = parts.first;
+    double width;
+    double height;
+    if (parts.length == 2) {
+      List<String> dimensions = parts.last.split('x');
+      if (dimensions.length == 2) {
+        width = double.parse(dimensions[0]);
+        height = double.parse(dimensions[1]);
+      }
+    }
+
+    return new NetworkImage(src: path, width: width, height: height);
+  }
+}
+
+abstract class SyntaxHighlighter {
+  TextSpan format(String source);
+}
+
+class _DefaultSyntaxHighlighter extends SyntaxHighlighter{
+  _DefaultSyntaxHighlighter(this.style);
+  final TextStyle style;
+
+  TextSpan format(String source) {
+    return new TextSpan(style: style, children: <TextSpan>[new TextSpan(text: source)]);
+  }
+}
diff --git a/packages/flutter_markdown/lib/src/markdown_style.dart b/packages/flutter_markdown/lib/src/markdown_style.dart
new file mode 100644
index 0000000..d71afce
--- /dev/null
+++ b/packages/flutter_markdown/lib/src/markdown_style.dart
@@ -0,0 +1,77 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'markdown_style_raw.dart';
+
+/// Style used for rendering markdown formatted text using the [MarkdownBody]
+/// widget.
+class MarkdownStyle extends MarkdownStyleRaw{
+
+  /// Creates a [MarkdownStyle] from the [TextStyle]s in the provided [theme].
+  MarkdownStyle.defaultFromTheme(ThemeData theme) : super(
+    a: new TextStyle(color: Colors.blue[500]),
+    p: theme.text.body1,
+    code: new TextStyle(
+      color: Colors.grey[700],
+      fontFamily: "monospace",
+      fontSize: theme.text.body1.fontSize * 0.85
+    ),
+    h1: theme.text.headline,
+    h2: theme.text.title,
+    h3: theme.text.subhead,
+    h4: theme.text.body2,
+    h5: theme.text.body2,
+    h6: theme.text.body2,
+    em: new TextStyle(fontStyle: FontStyle.italic),
+    strong: new TextStyle(fontWeight: FontWeight.bold),
+    blockquote: theme.text.body1,
+    blockSpacing: 8.0,
+    listIndent: 32.0,
+    blockquotePadding: 8.0,
+    blockquoteDecoration: new BoxDecoration(
+      backgroundColor: Colors.blue[100],
+      borderRadius: 2.0
+    ),
+    codeblockPadding: 8.0,
+    codeblockDecoration: new BoxDecoration(
+      backgroundColor: Colors.grey[100],
+      borderRadius: 2.0
+    )
+  );
+
+  /// Creates a [MarkdownStyle] from the [TextStyle]s in the provided [theme].
+  /// This style uses larger fonts for the headings than in
+  /// [MarkdownStyle.defaultFromTheme].
+  MarkdownStyle.largeFromTheme(ThemeData theme) : super (
+    a: new TextStyle(color: Colors.blue[500]),
+    p: theme.text.body1,
+    code: new TextStyle(
+      color: Colors.grey[700],
+      fontFamily: "monospace",
+      fontSize: theme.text.body1.fontSize * 0.85
+    ),
+    h1: theme.text.display3,
+    h2: theme.text.display2,
+    h3: theme.text.display1,
+    h4: theme.text.headline,
+    h5: theme.text.title,
+    h6: theme.text.subhead,
+    em: new TextStyle(fontStyle: FontStyle.italic),
+    strong: new TextStyle(fontWeight: FontWeight.bold),
+    blockquote: theme.text.body1,
+    blockSpacing: 8.0,
+    listIndent: 32.0,
+    blockquotePadding: 8.0,
+    blockquoteDecoration: new BoxDecoration(
+      backgroundColor: Colors.blue[100],
+      borderRadius: 2.0
+    ),
+    codeblockPadding: 8.0,
+    codeblockDecoration: new BoxDecoration(
+      backgroundColor: Colors.grey[100],
+      borderRadius: 2.0
+    )
+  );
+}
diff --git a/packages/flutter_markdown/lib/src/markdown_style_raw.dart b/packages/flutter_markdown/lib/src/markdown_style_raw.dart
new file mode 100644
index 0000000..4b69485
--- /dev/null
+++ b/packages/flutter_markdown/lib/src/markdown_style_raw.dart
@@ -0,0 +1,120 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/widgets.dart';
+
+/// Style used for rendering markdown formatted text using the [MarkdownBody]
+/// widget.
+class MarkdownStyleRaw {
+
+  /// Creates a new [MarkdownStyleRaw]
+  MarkdownStyleRaw({
+    this.a,
+    this.p,
+    this.code,
+    this.h1,
+    this.h2,
+    this.h3,
+    this.h4,
+    this.h5,
+    this.h6,
+    this.em,
+    this.strong,
+    this.blockquote,
+    this.blockSpacing,
+    this.listIndent,
+    this.blockquotePadding,
+    this.blockquoteDecoration,
+    this.codeblockPadding,
+    this.codeblockDecoration
+  }) {
+    _init();
+  }
+
+  /// Creates a new [MarkdownStyleRaw] based on the current style, with the
+  /// provided paramaters overridden.
+  MarkdownStyleRaw copyWith({
+    TextStyle a,
+    TextStyle p,
+    TextStyle code,
+    TextStyle h1,
+    TextStyle h2,
+    TextStyle h3,
+    TextStyle h4,
+    TextStyle h5,
+    TextStyle h6,
+    TextStyle em,
+    TextStyle strong,
+    TextStyle blockquote,
+    double blockSpacing,
+    double listIndent,
+    double blockquotePadding,
+    BoxDecoration blockquoteDecoration,
+    double codeblockPadding,
+    BoxDecoration codeblockDecoration
+  }) {
+    return new MarkdownStyleRaw(
+      a: a != null ? a : this.a,
+      p: p != null ? p : this.p,
+      code: code != null ? code : this.code,
+      h1: h1 != null ? h1 : this.h1,
+      h2: h2 != null ? h2 : this.h2,
+      h3: h3 != null ? h3 : this.h3,
+      h4: h4 != null ? h4 : this.h4,
+      h5: h5 != null ? h5 : this.h5,
+      h6: h6 != null ? h6 : this.h6,
+      em: em != null ? em : this.em,
+      strong: strong != null ? strong : this.strong,
+      blockquote: blockquote != null ? blockquote : this.blockquote,
+      blockSpacing: blockSpacing != null ? blockSpacing : this.blockSpacing,
+      listIndent: listIndent != null ? listIndent : this.listIndent,
+      blockquotePadding: blockquotePadding != null ? blockquotePadding : this.blockquotePadding,
+      blockquoteDecoration: blockquoteDecoration != null ? blockquoteDecoration : this.blockquoteDecoration,
+      codeblockPadding: codeblockPadding != null ? codeblockPadding : this.codeblockPadding,
+      codeblockDecoration: codeblockDecoration != null ? codeblockDecoration : this.codeblockDecoration
+    );
+  }
+
+  final TextStyle a;
+  final TextStyle p;
+  final TextStyle code;
+  final TextStyle h1;
+  final TextStyle h2;
+  final TextStyle h3;
+  final TextStyle h4;
+  final TextStyle h5;
+  final TextStyle h6;
+  final TextStyle em;
+  final TextStyle strong;
+  final TextStyle blockquote;
+  final double blockSpacing;
+  final double listIndent;
+  final double blockquotePadding;
+  final BoxDecoration blockquoteDecoration;
+  final double codeblockPadding;
+  final BoxDecoration codeblockDecoration;
+
+  Map<String, TextStyle> _styles;
+
+  Map<String, TextStyle> get styles => _styles;
+
+  void _init() {
+    _styles = {
+      'a': a,
+      'p': p,
+      'li': p,
+      'code': code,
+      'pre': p,
+      'h1': h1,
+      'h2': h2,
+      'h3': h3,
+      'h4': h4,
+      'h5': h5,
+      'h6': h6,
+      'em': em,
+      'strong': strong,
+      'blockquote': blockquote
+    };
+  }
+}
diff --git a/packages/flutter_markdown/pubspec.yaml b/packages/flutter_markdown/pubspec.yaml
new file mode 100644
index 0000000..2a85b01
--- /dev/null
+++ b/packages/flutter_markdown/pubspec.yaml
@@ -0,0 +1,18 @@
+name: flutter_markdown
+description: A markdown renderer for Flutter.
+version: 0.1.0
+author: Flutter Authors <flutter-dev@googlegroups.com>
+homepage: http://flutter.io
+
+dependencies:
+  flutter:
+    path: ../flutter
+  markdown: "0.9.0"
+  string_scanner: "0.1.4+1"
+
+dev_dependencies:
+  flutter_tools:
+    path: ../flutter_tools
+  test: any # constrained by the dependency in flutter_tools
+  flutter_test:
+    path: ../flutter_test
diff --git a/packages/flutter_markdown/test/flutter_markdown_test.dart b/packages/flutter_markdown/test/flutter_markdown_test.dart
new file mode 100644
index 0000000..f738572
--- /dev/null
+++ b/packages/flutter_markdown/test/flutter_markdown_test.dart
@@ -0,0 +1,134 @@
+import 'package:flutter_markdown/flutter_markdown.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/widgets.dart';
+import 'package:test/test.dart';
+import 'package:flutter/material.dart';
+
+void main() {
+  test("Simple string", () {
+    testWidgets((WidgetTester tester) {
+      tester.pumpWidget(new MarkdownBody(data: "Hello"));
+
+      Element textElement = tester.findElement((Element element) => element.widget is RichText);
+      RichText textWidget = textElement.widget;
+      TextSpan textSpan = textWidget.text;
+
+      List<Element> elements = _listElements(tester);
+      _expectWidgetTypes(elements, <Type>[MarkdownBody, Column, Container, Padding, RichText]);
+      expect(textSpan.children[0].text, equals("Hello"));
+    });
+  });
+
+  test("Header", () {
+    testWidgets((WidgetTester tester) {
+      tester.pumpWidget(new MarkdownBody(data: "# Header"));
+
+      Element textElement = tester.findElement((Element element) => element.widget is RichText);
+      RichText textWidget = textElement.widget;
+      TextSpan textSpan = textWidget.text;
+
+      List<Element> elements = _listElements(tester);
+      _expectWidgetTypes(elements, <Type>[MarkdownBody, Column, Container, Padding, RichText]);
+      expect(textSpan.children[0].text, equals("Header"));
+    });
+  });
+
+  test("Empty string", () {
+    testWidgets((WidgetTester tester) {
+      tester.pumpWidget(new MarkdownBody(data: ""));
+
+      List<Element> elements = _listElements(tester);
+      _expectWidgetTypes(elements, <Type>[MarkdownBody, Column]);
+    });
+  });
+
+  test("Ordered list", () {
+    testWidgets((WidgetTester tester) {
+      tester.pumpWidget(new MarkdownBody(data: "1. Item 1\n1. Item 2\n2. Item 3"));
+
+      List<Element> elements = _listElements(tester);
+      _expectTextStrings(elements, <String>[
+        "1.",
+        "Item 1",
+        "2.",
+        "Item 2",
+        "3.",
+        "Item 3"]
+      );
+    });
+  });
+
+  test("Unordered list", () {
+    testWidgets((WidgetTester tester) {
+      tester.pumpWidget(new MarkdownBody(data: "- Item 1\n- Item 2\n- Item 3"));
+
+      List<Element> elements = _listElements(tester);
+      _expectTextStrings(elements, <String>[
+        "•",
+        "Item 1",
+        "•",
+        "Item 2",
+        "•",
+        "Item 3"]
+      );
+    });
+  });
+
+  test("Scrollable wrapping", () {
+    testWidgets((WidgetTester tester) {
+      tester.pumpWidget(new Markdown(data: ""));
+
+      List<Element> elements = _listElements(tester);
+      for (Element element in elements) print("e: $element");
+      _expectWidgetTypes(elements, <Type>[
+        Markdown,
+        ScrollableViewport,
+        null, null, null, null, null, // ScrollableViewport internals
+        Padding,
+        MarkdownBody,
+        Column
+      ]);
+    });
+  });
+}
+
+List<Element> _listElements(WidgetTester tester) {
+  List<Element> elements = <Element>[];
+  tester.walkElements((Element element) {
+    elements.add(element);
+  });
+  return elements;
+}
+
+void _expectWidgetTypes(List<Element> elements, List<Type> types) {
+  expect(elements.length, equals(types.length));
+  for (int i = 0; i < elements.length; i += 1) {
+    Element element = elements[i];
+    Type type = types[i];
+    if (type == null) continue;
+    expect(element.widget.runtimeType, equals(type));
+  }
+}
+
+void _expectTextStrings(List<Element> elements, List<String> strings) {
+  int currentString = 0;
+  for (Element element in elements) {
+    Widget widget = element.widget;
+    if (widget is RichText) {
+      TextSpan span = widget.text;
+      String text = _extractTextFromTextSpan(span);
+      expect(text, equals(strings[currentString]));
+      currentString += 1;
+    }
+  }
+}
+
+String _extractTextFromTextSpan(TextSpan span) {
+  String text = span.text ?? "";
+  if (span.children != null) {
+    for (TextSpan child in span.children) {
+      text += _extractTextFromTextSpan(child);
+    }
+  }
+  return text;
+}