Reland "SkParagraph"

This is a reland of 10ad0b9b01e4b8a4721ae2ec1adee9ca7d0fe534

Original change's description:
> SkParagraph
>
> Change-Id: I0a4be75fd0c18021c201bcc1edfdfad8556edeff
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/192100
> Reviewed-by: Ben Wagner <bungeman@google.com>
> Reviewed-by: Mike Reed <reed@google.com>
> Commit-Queue: Julia Lavrova <jlavrova@google.com>

Change-Id: I46cf43eae693edf68e45345acd0eb39e04e02bfc
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/219863
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Julia Lavrova <jlavrova@google.com>
diff --git a/modules/skottie/BUILD.gn b/modules/skottie/BUILD.gn
index be15434..fd857a7 100644
--- a/modules/skottie/BUILD.gn
+++ b/modules/skottie/BUILD.gn
@@ -39,8 +39,9 @@
 
       public_configs = [ ":utils_config" ]
       configs += [ "../../:skia_private" ]
+
       sources = [
-        "utils/SkottieUtils.cpp",
+          "utils/SkottieUtils.cpp",
       ]
       deps = [
         ":skottie",
diff --git a/modules/skparagraph/BUILD.gn b/modules/skparagraph/BUILD.gn
new file mode 100644
index 0000000..a0a129d
--- /dev/null
+++ b/modules/skparagraph/BUILD.gn
@@ -0,0 +1,80 @@
+# Copyright 2019 Google LLC.
+
+import("../../gn/skia.gni")
+
+declare_args() {
+  skia_enable_skparagraph = !(is_win && is_component_build)
+  paragraph_tests_enabled = true
+  paragraph_bench_enabled = false
+}
+
+if (skia_enable_skparagraph) {
+
+  config("public_config") {
+    include_dirs = [ "include" ]
+  }
+
+  component("skparagraph") {
+    import("skparagraph.gni")
+    public_configs = [ ":public_config" ]
+    public = skparagraph_public
+    if (skia_use_icu && skia_use_harfbuzz) {
+      sources = skparagraph_sources
+    } else {
+      sources = []
+    }
+    deps = [
+      "../..:skia",
+      "../skshaper",
+      "//third_party/icu",
+    ]
+  }
+
+  if (defined(is_skia_standalone) && skia_enable_tools) {
+    source_set("tests") {
+      if (skia_use_icu && skia_use_harfbuzz && paragraph_tests_enabled) {
+        testonly = true
+        sources = [
+          "//tests/SkParagraphTest.cpp"
+        ]
+        deps = [
+          "../..:skia",
+          "../..:gpu_tool_utils",
+          ":skparagraph",
+          "../skshaper",
+          "//third_party/icu",
+        ]
+      }
+    }
+
+    source_set("bench") {
+      if (skia_use_icu && skia_use_harfbuzz && paragraph_bench_enabled) {
+        testonly = true
+        sources = [
+          "//bench/ParagraphBench.cpp"
+        ]
+        deps = [
+          "../..:skia",
+          ":skparagraph",
+          "../skshaper",
+          "//third_party/icu",
+        ]
+      }
+    }
+
+    source_set("samples") {
+      if (skia_use_icu && skia_use_harfbuzz) {
+        testonly = true
+        sources = [
+          "//samplecode/SampleParagraph.cpp",
+        ]
+        deps = [
+          "../..:skia",
+          ":skparagraph",
+          "../skshaper",
+          "//third_party/icu",
+        ]
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/modules/skparagraph/include/DartTypes.h b/modules/skparagraph/include/DartTypes.h
new file mode 100644
index 0000000..fbd3fd3
--- /dev/null
+++ b/modules/skparagraph/include/DartTypes.h
@@ -0,0 +1,98 @@
+// Copyright 2019 Google LLC.
+
+#ifndef DartTypes_DEFINED
+#define DartTypes_DEFINED
+
+#include "include/core/SkRect.h"
+
+namespace skia {
+namespace textlayout {
+
+enum Affinity { kUpstream, kDownstream };
+
+enum class RectHeightStyle {
+    // Provide tight bounding boxes that fit heights per run.
+    kTight,
+
+    // The height of the boxes will be the maximum height of all runs in the
+    // line. All rects in the same line will be the same height.
+    kMax,
+
+    // Extends the top and/or bottom edge of the bounds to fully cover any line
+    // spacing. The top edge of each line should be the same as the bottom edge
+    // of the line above. There should be no gaps in vertical coverage given any
+    // ParagraphStyle line_height.
+    //
+    // The top and bottom of each rect will cover half of the
+    // space above and half of the space below the line.
+    kIncludeLineSpacingMiddle,
+    // The line spacing will be added to the top of the rect.
+    kIncludeLineSpacingTop,
+    // The line spacing will be added to the bottom of the rect.
+    kIncludeLineSpacingBottom
+};
+
+enum class RectWidthStyle {
+    // Provide tight bounding boxes that fit widths to the runs of each line
+    // independently.
+    kTight,
+
+    // Extends the width of the last rect of each line to match the position of
+    // the widest rect over all the lines.
+    kMax
+};
+
+enum class TextAlign {
+    kLeft,
+    kRight,
+    kCenter,
+    kJustify,
+    kStart,
+    kEnd,
+};
+
+enum class TextDirection {
+    kRtl,
+    kLtr,
+};
+
+struct PositionWithAffinity {
+    int32_t position;
+    Affinity affinity;
+
+    PositionWithAffinity(int32_t p, Affinity a) : position(p), affinity(a) {}
+};
+
+struct TextBox {
+    SkRect rect;
+    TextDirection direction;
+
+    TextBox(SkRect r, TextDirection d) : rect(r), direction(d) {}
+};
+
+template <typename T> struct SkRange {
+    SkRange() : start(), end() {}
+    SkRange(T s, T e) : start(s), end(e) {}
+
+    T start, end;
+
+    bool operator==(const SkRange<T>& other) const {
+        return start == other.start && end == other.end;
+    }
+
+    T width() { return end - start; }
+
+    void Shift(T delta) {
+        start += delta;
+        end += delta;
+    }
+};
+
+enum class TextBaseline {
+    kAlphabetic,
+    kIdeographic,
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // DartTypes_DEFINED
diff --git a/modules/skparagraph/include/FontCollection.h b/modules/skparagraph/include/FontCollection.h
new file mode 100644
index 0000000..b678547
--- /dev/null
+++ b/modules/skparagraph/include/FontCollection.h
@@ -0,0 +1,68 @@
+// Copyright 2019 Google LLC.
+#ifndef FontCollection_DEFINED
+#define FontCollection_DEFINED
+
+#include <memory>
+#include <set>
+#include "modules/skparagraph/include/TextStyle.h"
+#include "include/core/SkFontMgr.h"
+#include "include/core/SkRefCnt.h"
+#include "include/private/SkTHash.h"
+
+namespace skia {
+namespace textlayout {
+
+class FontCollection : public SkRefCnt {
+public:
+    FontCollection();
+
+    ~FontCollection();
+
+    size_t getFontManagersCount() const;
+
+    void setAssetFontManager(sk_sp<SkFontMgr> fontManager);
+    void setDynamicFontManager(sk_sp<SkFontMgr> fontManager);
+    void setTestFontManager(sk_sp<SkFontMgr> fontManager);
+    void setDefaultFontManager(sk_sp<SkFontMgr> fontManager, const char defaultFamilyName[]);
+
+    sk_sp<SkFontMgr> geFallbackManager() const { return fDefaultFontManager; }
+
+    sk_sp<SkTypeface> matchTypeface(const char familyName[], SkFontStyle fontStyle);
+    sk_sp<SkTypeface> matchDefaultTypeface(SkFontStyle fontStyle);
+    sk_sp<SkTypeface> defaultFallback(SkUnichar unicode, SkFontStyle fontStyle);
+
+    void disableFontFallback();
+    bool fontFallbackEnabled() { return fEnableFontFallback; }
+
+private:
+    std::vector<sk_sp<SkFontMgr>> getFontManagerOrder() const;
+
+    struct FamilyKey {
+        FamilyKey(const char family[], const char loc[], SkFontStyle style)
+                : fFontFamily(family), fLocale(loc), fFontStyle(style) {}
+
+        FamilyKey() {}
+
+        SkString fFontFamily;
+        SkString fLocale;
+        SkFontStyle fFontStyle;
+
+        bool operator==(const FamilyKey& other) const;
+
+        struct Hasher {
+            size_t operator()(const FamilyKey& key) const;
+        };
+    };
+
+    bool fEnableFontFallback;
+    SkTHashMap<FamilyKey, sk_sp<SkTypeface>, FamilyKey::Hasher> fTypefaces;
+    sk_sp<SkFontMgr> fDefaultFontManager;
+    sk_sp<SkFontMgr> fAssetFontManager;
+    sk_sp<SkFontMgr> fDynamicFontManager;
+    sk_sp<SkFontMgr> fTestFontManager;
+    SkString fDefaultFamilyName;
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // FontCollection_DEFINED
diff --git a/modules/skparagraph/include/Paragraph.h b/modules/skparagraph/include/Paragraph.h
new file mode 100644
index 0000000..55cf3f4
--- /dev/null
+++ b/modules/skparagraph/include/Paragraph.h
@@ -0,0 +1,83 @@
+// Copyright 2019 Google LLC.
+#ifndef Paragraph_DEFINED
+#define Paragraph_DEFINED
+
+#include "modules/skparagraph/include/FontCollection.h"
+#include "modules/skparagraph/include/ParagraphStyle.h"
+#include "modules/skparagraph/include/TextStyle.h"
+
+class SkCanvas;
+
+namespace skia {
+namespace textlayout {
+
+struct Block {
+    Block(size_t start, size_t end, const TextStyle& style)
+            : fStart(start), fEnd(end), fStyle(style) {}
+    size_t fStart;
+    size_t fEnd;
+    TextStyle fStyle;
+};
+
+class Paragraph {
+
+public:
+    Paragraph(ParagraphStyle style, sk_sp<FontCollection> fonts)
+            : fFontCollection(std::move(fonts)), fParagraphStyle(std::move(style)) {}
+
+    virtual ~Paragraph() = default;
+
+    SkScalar getMaxWidth() { return fWidth; }
+
+    SkScalar getHeight() { return fHeight; }
+
+    SkScalar getMinIntrinsicWidth() { return fMinIntrinsicWidth; }
+
+    SkScalar getMaxIntrinsicWidth() { return fMaxIntrinsicWidth; }
+
+    SkScalar getAlphabeticBaseline() { return fAlphabeticBaseline; }
+
+    SkScalar getIdeographicBaseline() { return fIdeographicBaseline; }
+
+    virtual bool didExceedMaxLines() = 0;
+
+    virtual void layout(SkScalar width) = 0;
+
+    virtual void paint(SkCanvas* canvas, SkScalar x, SkScalar y) = 0;
+
+    // Returns a vector of bounding boxes that enclose all text between
+    // start and end glyph indexes, including start and excluding end
+    virtual std::vector<TextBox> getRectsForRange(unsigned start,
+                                                  unsigned end,
+                                                  RectHeightStyle rectHeightStyle,
+                                                  RectWidthStyle rectWidthStyle) = 0;
+
+    // Returns the index of the glyph that corresponds to the provided coordinate,
+    // with the top left corner as the origin, and +y direction as down
+    virtual PositionWithAffinity getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) = 0;
+
+    // Finds the first and last glyphs that define a word containing
+    // the glyph at index offset
+    virtual SkRange<size_t> getWordBoundary(unsigned offset) = 0;
+
+    virtual size_t lineNumber() = 0;
+
+protected:
+    friend class ParagraphBuilder;
+
+    sk_sp<FontCollection> fFontCollection;
+    ParagraphStyle fParagraphStyle;
+
+    // Things for Flutter
+    SkScalar fAlphabeticBaseline;
+    SkScalar fIdeographicBaseline;
+    SkScalar fHeight;
+    SkScalar fWidth;
+    SkScalar fMaxIntrinsicWidth;
+    SkScalar fMinIntrinsicWidth;
+    SkScalar fMaxLineWidth;
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // Paragraph_DEFINED
diff --git a/modules/skparagraph/include/ParagraphBuilder.h b/modules/skparagraph/include/ParagraphBuilder.h
new file mode 100644
index 0000000..50f02b2
--- /dev/null
+++ b/modules/skparagraph/include/ParagraphBuilder.h
@@ -0,0 +1,60 @@
+// Copyright 2019 Google LLC.
+#ifndef ParagraphBuilder_DEFINED
+#define ParagraphBuilder_DEFINED
+
+#include <memory>
+#include <stack>
+#include <string>
+#include <tuple>
+#include "modules/skparagraph/include/FontCollection.h"
+#include "modules/skparagraph/include/Paragraph.h"
+#include "modules/skparagraph/include/ParagraphStyle.h"
+#include "modules/skparagraph/include/TextStyle.h"
+
+namespace skia {
+namespace textlayout {
+
+class ParagraphBuilder {
+public:
+    ParagraphBuilder(ParagraphStyle style, sk_sp<FontCollection> fontCollection) { }
+
+    virtual ~ParagraphBuilder() = default;
+
+    // Push a style to the stack. The corresponding text added with AddText will
+    // use the top-most style.
+    virtual void pushStyle(const TextStyle& style) = 0;
+
+    // Remove a style from the stack. Useful to apply different styles to chunks
+    // of text such as bolding.
+    // Example:
+    //   builder.PushStyle(normal_style);
+    //   builder.AddText("Hello this is normal. ");
+    //
+    //   builder.PushStyle(bold_style);
+    //   builder.AddText("And this is BOLD. ");
+    //
+    //   builder.Pop();
+    //   builder.AddText(" Back to normal again.");
+    virtual void pop() = 0;
+
+    virtual TextStyle peekStyle() = 0;
+
+    // Adds text to the builder. Forms the proper runs to use the upper-most style
+    // on the style_stack_;
+    virtual void addText(const std::u16string& text) = 0;
+
+    // Converts to u16string before adding.
+    virtual void addText(const char* text) = 0;
+
+    virtual void setParagraphStyle(const ParagraphStyle& style) = 0;
+
+    // Constructs a SkParagraph object that can be used to layout and paint the text to a SkCanvas.
+    virtual std::unique_ptr<Paragraph> Build() = 0;
+
+    static std::shared_ptr<ParagraphBuilder> make(ParagraphStyle style,
+                                                  sk_sp<FontCollection> fontCollection);
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // ParagraphBuilder_DEFINED
diff --git a/modules/skparagraph/include/ParagraphStyle.h b/modules/skparagraph/include/ParagraphStyle.h
new file mode 100644
index 0000000..55ce1aa
--- /dev/null
+++ b/modules/skparagraph/include/ParagraphStyle.h
@@ -0,0 +1,98 @@
+// Copyright 2019 Google LLC.
+#ifndef ParagraphStyle_DEFINED
+#define ParagraphStyle_DEFINED
+
+#include "modules/skparagraph/include/DartTypes.h"
+#include "modules/skparagraph/include/TextStyle.h"
+#include "include/core/SkFontStyle.h"
+
+namespace skia {
+namespace textlayout {
+
+struct StrutStyle {
+    StrutStyle();
+
+    const std::vector<SkString>& getFontFamilies() const { return fFontFamilies; }
+    void setFontFamilies(std::vector<SkString> families) { fFontFamilies = std::move(families); }
+
+    SkFontStyle getFontStyle() const { return fFontStyle; }
+    void setFontStyle(SkFontStyle fontStyle) { fFontStyle = fontStyle; }
+
+    SkScalar getFontSize() const { return fFontSize; }
+    void setFontSize(SkScalar size) { fFontSize = size; }
+
+    void setHeight(SkScalar height) { fHeight = height; }
+    SkScalar getHeight() const { return fHeight; }
+
+    void setLeading(SkScalar Leading) { fLeading = Leading; }
+    SkScalar getLeading() const { return fLeading; }
+
+    bool getStrutEnabled() const { return fStrutEnabled; }
+    void setStrutEnabled(bool v) { fStrutEnabled = v; }
+
+    bool getForceStrutHeight() const { return fForceStrutHeight; }
+    void setForceStrutHeight(bool v) { fForceStrutHeight = v; }
+
+private:
+
+    std::vector<SkString> fFontFamilies;
+    SkFontStyle fFontStyle;
+    SkScalar fFontSize;
+    SkScalar fHeight;
+    SkScalar fLeading;
+    bool fForceStrutHeight;
+    bool fStrutEnabled;
+};
+
+struct ParagraphStyle {
+    ParagraphStyle();
+
+    bool operator==(const ParagraphStyle& rhs) const {
+        return this->fHeight == rhs.fHeight && this->fEllipsis == rhs.fEllipsis &&
+               this->fTextDirection == rhs.fTextDirection && this->fTextAlign == rhs.fTextAlign &&
+               this->fDefaultTextStyle == rhs.fDefaultTextStyle;
+    }
+
+    const StrutStyle& getStrutStyle() const { return fStrutStyle; }
+    void setStrutStyle(StrutStyle strutStyle) { fStrutStyle = std::move(strutStyle); }
+
+    const TextStyle& getTextStyle() const { return fDefaultTextStyle; }
+    void setTextStyle(const TextStyle& textStyle) { fDefaultTextStyle = textStyle; }
+
+    TextDirection getTextDirection() const { return fTextDirection; }
+    void setTextDirection(TextDirection direction) { fTextDirection = direction; }
+
+    TextAlign getTextAlign() const { return fTextAlign; }
+    void setTextAlign(TextAlign align) { fTextAlign = align; }
+
+    size_t getMaxLines() const { return fLinesLimit; }
+    void setMaxLines(size_t maxLines) { fLinesLimit = maxLines; }
+
+    const SkString& getEllipsis() const { return fEllipsis; }
+    void setEllipsis(const std::u16string& ellipsis);
+
+    SkScalar getHeight() const { return fHeight; }
+    void setHeight(SkScalar height) { fHeight = height; }
+
+    bool unlimited_lines() const {
+        return fLinesLimit == std::numeric_limits<size_t>::max();
+    }
+    bool ellipsized() const { return fEllipsis.size() != 0; }
+    TextAlign effective_align() const;
+    bool hintingIsOn() const { return fHintingIsOn; }
+    void turnHintingOff() { fHintingIsOn = false; }
+
+private:
+    StrutStyle fStrutStyle;
+    TextStyle fDefaultTextStyle;
+    TextAlign fTextAlign;
+    TextDirection fTextDirection;
+    size_t fLinesLimit;
+    SkString fEllipsis;
+    SkScalar fHeight;
+    bool fHintingIsOn;
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // ParagraphStyle_DEFINED
diff --git a/modules/skparagraph/include/TextShadow.h b/modules/skparagraph/include/TextShadow.h
new file mode 100644
index 0000000..a580b9c
--- /dev/null
+++ b/modules/skparagraph/include/TextShadow.h
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+#ifndef TextShadow_DEFINED
+#define TextShadow_DEFINED
+
+#include "include/core/SkColor.h"
+#include "include/core/SkPoint.h"
+
+namespace skia {
+namespace textlayout {
+
+class TextShadow {
+public:
+    SkColor fColor = SK_ColorBLACK;
+    SkPoint fOffset;
+    double fBlurRadius = 0.0;
+
+    TextShadow();
+
+    TextShadow(SkColor color, SkPoint offset, double blurRadius);
+
+    bool operator==(const TextShadow& other) const;
+
+    bool operator!=(const TextShadow& other) const;
+
+    bool hasShadow() const;
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // TextShadow_DEFINED
diff --git a/modules/skparagraph/include/TextStyle.h b/modules/skparagraph/include/TextStyle.h
new file mode 100644
index 0000000..9fad675
--- /dev/null
+++ b/modules/skparagraph/include/TextStyle.h
@@ -0,0 +1,168 @@
+// Copyright 2019 Google LLC.
+#ifndef TextStyle_DEFINED
+#define TextStyle_DEFINED
+
+#include <vector>
+#include "modules/skparagraph/include/DartTypes.h"
+#include "modules/skparagraph/include/TextShadow.h"
+#include "include/core/SkColor.h"
+#include "include/core/SkFont.h"
+#include "include/core/SkFontMetrics.h"
+#include "include/core/SkFontStyle.h"
+#include "include/core/SkPaint.h"
+
+// TODO: Make it external so the other platforms (Android) could use it
+#define DEFAULT_FONT_FAMILY "sans-serif"
+
+namespace skia {
+namespace textlayout {
+
+// Multiple decorations can be applied at once. Ex: Underline and overline is
+// (0x1 | 0x2)
+enum TextDecoration {
+    kNoDecoration = 0x0,
+    kUnderline = 0x1,
+    kOverline = 0x2,
+    kLineThrough = 0x4,
+};
+constexpr std::initializer_list<TextDecoration> AllTextDecorations = {
+        kNoDecoration,
+        kUnderline,
+        kOverline,
+        kLineThrough,
+};
+
+enum TextDecorationStyle { kSolid, kDouble, kDotted, kDashed, kWavy };
+
+enum StyleType {
+    kAllAttributes,
+    kText,
+    kFont,
+    kForeground,
+    kBackground,
+    kShadow,
+    kDecorations,
+    kLetterSpacing,
+    kWordSpacing
+};
+
+class TextStyle {
+public:
+    TextStyle();
+    ~TextStyle() = default;
+
+    bool equals(const TextStyle& other) const;
+    bool matchOneAttribute(StyleType styleType, const TextStyle& other) const;
+    bool operator==(const TextStyle& rhs) const { return this->equals(rhs); }
+
+    // Colors
+    SkColor getColor() const { return fColor; }
+    void setColor(SkColor color) { fColor = color; }
+
+    bool hasForeground() const { return fHasForeground; }
+    SkPaint getForeground() const { return fForeground; }
+    void setForegroundColor(SkPaint paint) {
+        fHasForeground = true;
+        fForeground = std::move(paint);
+    }
+    void clearForegroundColor() { fHasForeground = false; }
+
+    bool hasBackground() const { return fHasBackground; }
+    SkPaint getBackground() const { return fBackground; }
+    void setBackgroundColor(SkPaint paint) {
+        fHasBackground = true;
+        fBackground = std::move(paint);
+    }
+    void clearBackgroundColor() { fHasBackground = false; }
+
+    // Decorations
+    TextDecoration getDecoration() const { return fDecoration; }
+    SkColor getDecorationColor() const { return fDecorationColor; }
+    TextDecorationStyle getDecorationStyle() const { return fDecorationStyle; }
+    SkScalar getDecorationThicknessMultiplier() const {
+        return fDecorationThicknessMultiplier;
+    }
+    void setDecoration(TextDecoration decoration) { fDecoration = decoration; }
+    void setDecorationStyle(TextDecorationStyle style) { fDecorationStyle = style; }
+    void setDecorationColor(SkColor color) { fDecorationColor = color; }
+    void setDecorationThicknessMultiplier(SkScalar m) { fDecorationThicknessMultiplier = m; }
+
+    // Weight/Width/Slant
+    SkFontStyle getFontStyle() const { return fFontStyle; }
+    void setFontStyle(SkFontStyle fontStyle) { fFontStyle = fontStyle; }
+
+    // Shadows
+    size_t getShadowNumber() const { return fTextShadows.size(); }
+    std::vector<TextShadow> getShadows() const { return fTextShadows; }
+    void addShadow(TextShadow shadow) { fTextShadows.emplace_back(shadow); }
+    void resetShadows() { fTextShadows.clear(); }
+
+    SkScalar getFontSize() const { return fFontSize; }
+    void setFontSize(SkScalar size) { fFontSize = size; }
+
+    const std::vector<SkString>& getFontFamilies() const { return fFontFamilies; }
+    void setFontFamilies(std::vector<SkString> families) {
+        fFontFamilies = std::move(families);
+    }
+
+    void setHeight(SkScalar height) { fHeight = height; }
+    SkScalar getHeight() const { return fHeight; }
+
+    void setLetterSpacing(SkScalar letterSpacing) { fLetterSpacing = letterSpacing; }
+    SkScalar getLetterSpacing() const { return fLetterSpacing; }
+
+    void setWordSpacing(SkScalar wordSpacing) { fWordSpacing = wordSpacing; }
+    SkScalar getWordSpacing() const { return fWordSpacing; }
+
+    SkTypeface* getTypeface() const { return fTypeface.get(); }
+    sk_sp<SkTypeface> refTypeface() const { return fTypeface; }
+    void setTypeface(sk_sp<SkTypeface> typeface) { fTypeface = std::move(typeface); }
+
+    SkString getLocale() const { return fLocale; }
+    void setLocale(const SkString& locale) { fLocale = locale; }
+
+    TextBaseline getTextBaseline() const { return fTextBaseline; }
+    void setTextBaseline(TextBaseline baseline) { fTextBaseline = baseline; }
+
+    // TODO: Not to use SkFontMetrics class (it has different purpose and meaning)
+    void getFontMetrics(SkFontMetrics* metrics) const {
+        SkFont font(fTypeface, fFontSize);
+        font.getMetrics(metrics);
+        metrics->fAscent =
+                (metrics->fAscent - metrics->fLeading / 2) * (fHeight == 0 ? 1 : fHeight);
+        metrics->fDescent =
+                (metrics->fDescent + metrics->fLeading / 2) * (fHeight == 0 ? 1 : fHeight);
+    }
+
+private:
+    TextDecoration fDecoration;
+    SkColor fDecorationColor;
+    TextDecorationStyle fDecorationStyle;
+    SkScalar fDecorationThicknessMultiplier;
+
+    SkFontStyle fFontStyle;
+
+    std::vector<SkString> fFontFamilies;
+    SkScalar fFontSize;
+
+    SkScalar fHeight;
+    SkString fLocale;
+    SkScalar fLetterSpacing;
+    SkScalar fWordSpacing;
+
+    TextBaseline fTextBaseline;
+
+    SkColor fColor;
+    bool fHasBackground;
+    SkPaint fBackground;
+    bool fHasForeground;
+    SkPaint fForeground;
+
+    std::vector<TextShadow> fTextShadows;
+
+    sk_sp<SkTypeface> fTypeface;
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // TextStyle_DEFINED
diff --git a/modules/skparagraph/skparagraph.gni b/modules/skparagraph/skparagraph.gni
new file mode 100644
index 0000000..896df6b
--- /dev/null
+++ b/modules/skparagraph/skparagraph.gni
@@ -0,0 +1,36 @@
+# Copyright 2019 Google LLC.
+
+# Things are easiest for everyone if these source paths are absolute.
+_src = get_path_info("src", "abspath")
+_include = get_path_info("include", "abspath")
+
+skparagraph_public = [
+  "$_include/ParagraphStyle.h",
+  "$_include/TextStyle.h",
+  "$_include/TextShadow.h",
+  "$_include/FontCollection.h",
+  "$_include/Paragraph.h",
+  "$_include/ParagraphBuilder.h",
+  "$_include/DartTypes.h",
+]
+
+skparagraph_sources = [
+  "$_src/FontCollection.cpp",
+  "$_src/FontIterator.h",
+  "$_src/FontIterator.cpp",
+  "$_src/TextLine.h",
+  "$_src/TextLine.cpp",
+  "$_src/ParagraphBuilderImpl.h",
+  "$_src/ParagraphBuilderImpl.cpp",
+  "$_src/ParagraphImpl.h",
+  "$_src/ParagraphImpl.cpp",
+  "$_src/ParagraphStyle.cpp",
+  "$_src/Run.h",
+  "$_src/Run.cpp",
+  "$_src/TextShadow.cpp",
+  "$_src/TextStyle.cpp",
+  "$_src/TextWrapper.h",
+  "$_src/TextWrapper.cpp",
+  "$_src/TypefaceFontProvider.h",
+  "$_src/TypefaceFontProvider.cpp",
+]
diff --git a/modules/skparagraph/src/FontCollection.cpp b/modules/skparagraph/src/FontCollection.cpp
new file mode 100644
index 0000000..c085b99
--- /dev/null
+++ b/modules/skparagraph/src/FontCollection.cpp
@@ -0,0 +1,141 @@
+// Copyright 2019 Google LLC.
+#include "modules/skparagraph/include/FontCollection.h"
+#include <string>
+
+namespace skia {
+namespace textlayout {
+
+bool FontCollection::FamilyKey::operator==(const FontCollection::FamilyKey& other) const {
+    return fFontFamily == other.fFontFamily && fLocale == other.fLocale &&
+           fFontStyle == other.fFontStyle;
+}
+
+size_t FontCollection::FamilyKey::Hasher::operator()(const FontCollection::FamilyKey& key) const {
+    return std::hash<std::string>()(key.fFontFamily.c_str()) ^
+           std::hash<std::string>()(key.fLocale.c_str()) ^
+           std::hash<uint32_t>()(key.fFontStyle.weight()) ^
+           std::hash<uint32_t>()(key.fFontStyle.slant());
+}
+
+FontCollection::FontCollection()
+        : fEnableFontFallback(true)
+        , fDefaultFontManager(SkFontMgr::RefDefault())
+        , fDefaultFamilyName(DEFAULT_FONT_FAMILY) {}
+
+FontCollection::~FontCollection() = default;
+
+size_t FontCollection::getFontManagersCount() const { return this->getFontManagerOrder().size(); }
+
+void FontCollection::setAssetFontManager(sk_sp<SkFontMgr> font_manager) {
+    fAssetFontManager = font_manager;
+}
+
+void FontCollection::setDynamicFontManager(sk_sp<SkFontMgr> font_manager) {
+    fDynamicFontManager = font_manager;
+}
+
+void FontCollection::setTestFontManager(sk_sp<SkFontMgr> font_manager) {
+    fTestFontManager = font_manager;
+}
+
+void FontCollection::setDefaultFontManager(sk_sp<SkFontMgr> fontManager,
+                                           const char defaultFamilyName[]) {
+    fDefaultFontManager = fontManager;
+    fDefaultFamilyName = defaultFamilyName;
+}
+
+// Return the available font managers in the order they should be queried.
+std::vector<sk_sp<SkFontMgr>> FontCollection::getFontManagerOrder() const {
+    std::vector<sk_sp<SkFontMgr>> order;
+    if (fDynamicFontManager) {
+        order.push_back(fDynamicFontManager);
+    }
+    if (fAssetFontManager) {
+        order.push_back(fAssetFontManager);
+    }
+    if (fTestFontManager) {
+        order.push_back(fTestFontManager);
+    }
+    if (fDefaultFontManager && fEnableFontFallback) {
+        order.push_back(fDefaultFontManager);
+    }
+    return order;
+}
+
+sk_sp<SkTypeface> FontCollection::matchTypeface(const char familyName[], SkFontStyle fontStyle) {
+    // Look inside the font collections cache first
+    FamilyKey familyKey(familyName, "en", fontStyle);
+    auto found = fTypefaces.find(familyKey);
+    if (found) {
+        return *found;
+    }
+
+    sk_sp<SkTypeface> typeface = nullptr;
+    for (const auto& manager : this->getFontManagerOrder()) {
+        SkFontStyleSet* set = manager->matchFamily(familyName);
+        if (nullptr == set || set->count() == 0) {
+            continue;
+        }
+
+        for (int i = 0; i < set->count(); ++i) {
+            set->createTypeface(i);
+        }
+
+        sk_sp<SkTypeface> match(set->matchStyle(fontStyle));
+        if (match) {
+            typeface = std::move(match);
+            return typeface;
+        }
+    }
+
+    return nullptr;
+}
+
+sk_sp<SkTypeface> FontCollection::matchDefaultTypeface(SkFontStyle fontStyle) {
+    // Look inside the font collections cache first
+    FamilyKey familyKey(fDefaultFamilyName.c_str(), "en", fontStyle);
+    auto found = fTypefaces.find(familyKey);
+    if (found) {
+        return *found;
+    }
+
+    sk_sp<SkTypeface> typeface = nullptr;
+    for (const auto& manager : this->getFontManagerOrder()) {
+        SkFontStyleSet* set = manager->matchFamily(fDefaultFamilyName.c_str());
+        if (nullptr == set || set->count() == 0) {
+            continue;
+        }
+
+        for (int i = 0; i < set->count(); ++i) {
+            set->createTypeface(i);
+        }
+
+        sk_sp<SkTypeface> match(set->matchStyle(fontStyle));
+        if (match) {
+            typeface = std::move(match);
+            return typeface;
+        }
+    }
+
+    return nullptr;
+}
+
+sk_sp<SkTypeface> FontCollection::defaultFallback(SkUnichar unicode, SkFontStyle fontStyle) {
+
+    for (const auto& manager : this->getFontManagerOrder()) {
+        std::vector<const char*> bcp47;
+        sk_sp<SkTypeface> typeface(manager->matchFamilyStyleCharacter(
+                0, fontStyle, bcp47.data(), bcp47.size(), unicode));
+        if (typeface != nullptr) {
+            return typeface;
+        }
+    }
+
+    auto result = fDefaultFontManager->matchFamilyStyle(fDefaultFamilyName.c_str(), fontStyle);
+    return sk_ref_sp<SkTypeface>(result);
+}
+
+void FontCollection::disableFontFallback() { fEnableFontFallback = false; }
+
+}  // namespace textlayout
+}  // namespace skia
diff --git a/modules/skparagraph/src/FontIterator.cpp b/modules/skparagraph/src/FontIterator.cpp
new file mode 100644
index 0000000..13f5359
--- /dev/null
+++ b/modules/skparagraph/src/FontIterator.cpp
@@ -0,0 +1,265 @@
+// Copyright 2019 Google LLC.
+#include "modules/skparagraph/src/FontIterator.h"
+#include <unicode/brkiter.h>
+#include <unicode/ubidi.h>
+#include "include/core/SkBlurTypes.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkFontMgr.h"
+#include "include/core/SkPictureRecorder.h"
+#include "modules/skparagraph/src/ParagraphImpl.h"
+#include "src/core/SkSpan.h"
+#include "src/utils/SkUTF.h"
+
+namespace {
+SkUnichar utf8_next(const char** ptr, const char* end) {
+    SkUnichar val = SkUTF::NextUTF8(ptr, end);
+    return val < 0 ? 0xFFFD : val;
+}
+}  // namespace
+
+// TODO: FontCollection and FontIterator have common functionality
+namespace skia {
+namespace textlayout {
+
+FontIterator::FontIterator(SkSpan<const char> utf8,
+                           SkSpan<TextBlock>
+                                   styles,
+                           sk_sp<FontCollection>
+                                   fonts,
+                           bool hintingOn)
+        : fText(utf8)
+        , fStyles(styles)
+        , fCurrentChar(utf8.begin())
+        , fFontCollection(std::move(fonts))
+        , fHintingOn(hintingOn) {
+    findAllFontsForAllStyledBlocks();
+}
+
+void FontIterator::consume() {
+    SkASSERT(fCurrentChar < fText.end());
+    auto found = fFontMapping.find(fCurrentChar);
+    SkASSERT(found != nullptr);
+    fFont = found->first;
+    fLineHeight = found->second;
+
+    // Move until we find the first character that cannot be resolved with the current font
+    while (++fCurrentChar != fText.end()) {
+        found = fFontMapping.find(fCurrentChar);
+        if (found != nullptr) {
+            if (fFont == found->first && fLineHeight == found->second) {
+                continue;
+            }
+            break;
+        }
+    }
+}
+
+void FontIterator::findAllFontsForAllStyledBlocks() {
+    TextBlock combined;
+    for (auto& block : fStyles) {
+        SkASSERT(combined.text().begin() == nullptr ||
+                 combined.text().end() == block.text().begin());
+
+        if (combined.text().begin() != nullptr &&
+            block.style().matchOneAttribute(StyleType::kFont, combined.style())) {
+            combined.add(block.text());
+            continue;
+        }
+
+        if (!combined.text().empty()) {
+            findAllFontsForStyledBlock(combined.style(), combined.text());
+        }
+
+        combined = block;
+    }
+    findAllFontsForStyledBlock(combined.style(), combined.text());
+
+    if (!fText.empty() && fFontMapping.find(fText.begin()) == nullptr) {
+        // Resolve the first character with the first found font
+        fFontMapping.set(fText.begin(), fFirstResolvedFont);
+    }
+}
+
+void FontIterator::findAllFontsForStyledBlock(const TextStyle& style, SkSpan<const char> text) {
+    fCodepoints.reset();
+    fCharacters.reset();
+    fUnresolvedIndexes.reset();
+    fUnresolvedCodepoints.reset();
+
+    // Extract all unicode codepoints
+    const char* current = text.begin();
+    while (current != text.end()) {
+        fCharacters.emplace_back(current);
+        fCodepoints.emplace_back(utf8_next(&current, text.end()));
+        fUnresolvedIndexes.emplace_back(fUnresolvedIndexes.size());
+    }
+    fUnresolved = fCodepoints.size();
+
+    // Walk through all available fonts to resolve the block
+    for (auto& fontFamily : style.getFontFamilies()) {
+        auto typeface = fFontCollection->matchTypeface(fontFamily.c_str(), style.getFontStyle());
+        if (typeface.get() == nullptr) {
+            continue;
+        }
+
+        // Resolve all unresolved characters
+        auto font = makeFont(typeface, style.getFontSize(), style.getHeight());
+        resolveAllCharactersByFont(font);
+        if (fUnresolved == 0) {
+            break;
+        }
+    }
+
+    if (fUnresolved > 0) {
+        auto typeface = fFontCollection->matchDefaultTypeface(style.getFontStyle());
+        if (typeface.get() != nullptr) {
+            // Resolve all unresolved characters
+            auto font = makeFont(typeface, style.getFontSize(), style.getHeight());
+            resolveAllCharactersByFont(font);
+        }
+    }
+
+    addResolvedWhitespacesToMapping();
+
+    if (fUnresolved > 0 && fFontCollection->fontFallbackEnabled()) {
+        while (fUnresolved > 0) {
+            auto unicode = firstUnresolved();
+            auto typeface = fFontCollection->defaultFallback(unicode, style.getFontStyle());
+            if (typeface == nullptr) {
+                break;
+            }
+            auto font = makeFont(typeface, style.getFontSize(), style.getHeight());
+            if (!resolveAllCharactersByFont(font)) {
+                // Not a single unicode character was resolved
+                break;
+            }
+            SkString name;
+            typeface->getFamilyName(&name);
+            SkDebugf("Default font fallback resolution: %s\n", name.c_str());
+        }
+    }
+
+    // In case something still unresolved
+    if (fResolvedFonts.count() == 0) {
+        makeFont(fFontCollection->defaultFallback(firstUnresolved(), style.getFontStyle()),
+                 style.getFontSize(), style.getHeight());
+        if (fFirstResolvedFont.first.getTypeface() != nullptr) {
+            SkString name;
+            fFirstResolvedFont.first.getTypeface()->getFamilyName(&name);
+            SkDebugf("Urgent font resolution: %s\n", name.c_str());
+        } else {
+            SkDebugf("No font!!!\n");
+        }
+    }
+}
+
+size_t FontIterator::resolveAllCharactersByFont(std::pair<SkFont, SkScalar> font) {
+    // Consolidate all unresolved unicodes in one array to make a batch call
+    SkTArray<SkGlyphID> glyphs(fUnresolved);
+    glyphs.push_back_n(fUnresolved, SkGlyphID(0));
+    font.first.getTypeface()->unicharsToGlyphs(
+            fUnresolved == fCodepoints.size() ? fCodepoints.data() : fUnresolvedCodepoints.data(),
+            fUnresolved, glyphs.data());
+
+    SkRange<size_t> resolved(0, 0);
+    SkRange<size_t> whitespaces(0, 0);
+    size_t stillUnresolved = 0;
+
+    auto processRuns = [&]() {
+        if (resolved.width() == 0) {
+            return;
+        }
+
+        if (resolved.width() == whitespaces.width()) {
+            // The entire run is just whitespaces;
+            // Remember the font and mark whitespaces back unresolved
+            // to calculate its mapping for the other fonts
+            for (auto w = whitespaces.start; w != whitespaces.end; ++w) {
+                if (fWhitespaces.find(w) == nullptr) {
+                    fWhitespaces.set(w, font);
+                }
+                fUnresolvedIndexes[stillUnresolved++] = w;
+                fUnresolvedCodepoints.emplace_back(fCodepoints[w]);
+            }
+        } else {
+            fFontMapping.set(fCharacters[resolved.start], font);
+        }
+    };
+
+    // Try to resolve all the unresolved unicode points
+    for (size_t i = 0; i < glyphs.size(); ++i) {
+        auto glyph = glyphs[i];
+        auto index = fUnresolvedIndexes[i];
+
+        if (glyph == 0) {
+            processRuns();
+
+            resolved = SkRange<size_t>(0, 0);
+            whitespaces = SkRange<size_t>(0, 0);
+
+            fUnresolvedIndexes[stillUnresolved++] = index;
+            fUnresolvedCodepoints.emplace_back(fCodepoints[index]);
+            continue;
+        }
+
+        if (index == resolved.end) {
+            ++resolved.end;
+        } else {
+            processRuns();
+            resolved = SkRange<size_t>(index, index + 1);
+        }
+        if (u_isUWhiteSpace(fCodepoints[index])) {
+            if (index == whitespaces.end) {
+                ++whitespaces.end;
+            } else {
+                whitespaces = SkRange<size_t>(index, index + 1);
+            }
+        } else {
+            whitespaces = SkRange<size_t>(0, 0);
+        }
+    }
+
+    // One last time to take care of the tail run
+    processRuns();
+
+    size_t wasUnresolved = fUnresolved;
+    fUnresolved = stillUnresolved;
+    return fUnresolved < wasUnresolved;
+}
+
+void FontIterator::addResolvedWhitespacesToMapping() {
+    size_t resolvedWhitespaces = 0;
+    for (size_t i = 0; i < fUnresolved; ++i) {
+        auto index = fUnresolvedIndexes[i];
+        auto found = fWhitespaces.find(index);
+        if (found != nullptr) {
+            fFontMapping.set(fCharacters[index], *found);
+            ++resolvedWhitespaces;
+        }
+    }
+    fUnresolved -= resolvedWhitespaces;
+}
+
+std::pair<SkFont, SkScalar> FontIterator::makeFont(sk_sp<SkTypeface> typeface,
+                                                   SkScalar size,
+                                                   SkScalar height) {
+    SkFont font(typeface, size);
+    font.setEdging(SkFont::Edging::kAntiAlias);
+    if (!fHintingOn) {
+        font.setHinting(SkFontHinting::kSlight);
+        font.setSubpixel(true);
+    }
+    auto pair = std::make_pair(font, height);
+
+    auto foundFont = fResolvedFonts.find(pair);
+    if (foundFont == nullptr) {
+        if (fResolvedFonts.count() == 0) {
+            fFirstResolvedFont = pair;
+        }
+        fResolvedFonts.add(pair);
+    }
+
+    return pair;
+}
+}  // namespace textlayout
+}  // namespace skia
diff --git a/modules/skparagraph/src/FontIterator.h b/modules/skparagraph/src/FontIterator.h
new file mode 100644
index 0000000..68858ec
--- /dev/null
+++ b/modules/skparagraph/src/FontIterator.h
@@ -0,0 +1,79 @@
+// Copyright 2019 Google LLC.
+#ifndef FontIterator_DEFINED
+#define FontIterator_DEFINED
+
+#include <unicode/brkiter.h>
+#include <unicode/ubidi.h>
+#include "modules/skparagraph/src/ParagraphImpl.h"
+#include "include/core/SkBlurTypes.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkFontMgr.h"
+#include "include/core/SkPictureRecorder.h"
+#include "src/core/SkSpan.h"
+#include "src/utils/SkUTF.h"
+
+namespace skia {
+namespace textlayout {
+
+class FontIterator final : public SkShaper::FontRunIterator {
+public:
+    FontIterator(SkSpan<const char> utf8,
+                   SkSpan<TextBlock> styles,
+                   sk_sp<FontCollection> fonts,
+                   bool hintingOn);
+
+    void consume() override;
+
+    size_t endOfCurrentRun() const override { return fCurrentChar - fText.begin(); }
+    bool atEnd() const override { return fCurrentChar == fText.end(); }
+    const SkFont& currentFont() const override { return fFont; }
+    SkScalar lineHeight() const { return fLineHeight; }
+
+private:
+    struct Hash {
+        uint32_t operator()(const std::pair<SkFont, SkScalar>& key) const {
+            return SkTypeface::UniqueID(key.first.getTypeface()) +
+                   SkScalarCeilToInt(key.first.getSize()) + SkScalarCeilToInt(key.second);
+        }
+    };
+
+    void findAllFontsForAllStyledBlocks();
+
+    void findAllFontsForStyledBlock(const TextStyle& style, SkSpan<const char> text);
+
+    std::pair<SkFont, SkScalar> makeFont(sk_sp<SkTypeface> typeface, SkScalar size,
+                                         SkScalar height);
+
+    size_t resolveAllCharactersByFont(std::pair<SkFont, SkScalar> font);
+    void addResolvedWhitespacesToMapping();
+
+    SkUnichar firstUnresolved() {
+        if (fUnresolved == 0) return 0;
+
+        bool firstTry = fUnresolved == fCodepoints.size();
+        auto index = firstTry ? 0 : fUnresolvedIndexes[0];
+        return fCodepoints[index];
+    }
+
+    SkSpan<const char> fText;
+    SkSpan<TextBlock> fStyles;
+    const char* fCurrentChar;
+    SkFont fFont;
+    SkScalar fLineHeight;
+    sk_sp<FontCollection> fFontCollection;
+    SkTHashMap<const char*, std::pair<SkFont, SkScalar>> fFontMapping;
+    SkTHashSet<std::pair<SkFont, SkScalar>, Hash> fResolvedFonts;
+    bool fHintingOn;
+    std::pair<SkFont, SkScalar> fFirstResolvedFont;
+
+    SkTArray<SkUnichar> fCodepoints;
+    SkTArray<const char*> fCharacters;
+    SkTArray<size_t> fUnresolvedIndexes;
+    SkTArray<SkUnichar> fUnresolvedCodepoints;
+    SkTHashMap<size_t, std::pair<SkFont, SkScalar>> fWhitespaces;
+    size_t fUnresolved;
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // FontIterator_DEFINED
diff --git a/modules/skparagraph/src/ParagraphBuilderImpl.cpp b/modules/skparagraph/src/ParagraphBuilderImpl.cpp
new file mode 100644
index 0000000..0a97527
--- /dev/null
+++ b/modules/skparagraph/src/ParagraphBuilderImpl.cpp
@@ -0,0 +1,106 @@
+// Copyright 2019 Google LLC.
+#include "modules/skparagraph/src/ParagraphBuilderImpl.h"
+#include "include/core/SkPaint.h"
+#include "modules/skparagraph/include/ParagraphStyle.h"
+#include "modules/skparagraph/src/ParagraphImpl.h"
+#include "src/core/SkMakeUnique.h"
+#include "src/core/SkSpan.h"
+#include "unicode/unistr.h"
+
+namespace skia {
+namespace textlayout {
+
+std::shared_ptr<ParagraphBuilder> ParagraphBuilder::make(
+        ParagraphStyle style, sk_sp<FontCollection> fontCollection) {
+    return std::make_shared<ParagraphBuilderImpl>(style, fontCollection);
+}
+
+ParagraphBuilderImpl::ParagraphBuilderImpl(
+        ParagraphStyle style, sk_sp<FontCollection> fontCollection)
+        : ParagraphBuilder(style, fontCollection), fFontCollection(std::move(fontCollection)) {
+    this->setParagraphStyle(style);
+}
+
+ParagraphBuilderImpl::~ParagraphBuilderImpl() = default;
+
+void ParagraphBuilderImpl::setParagraphStyle(const ParagraphStyle& style) {
+    fParagraphStyle = style;
+    fTextStyles.push(fParagraphStyle.getTextStyle());
+    fStyledBlocks.emplace_back(fUtf8.size(), fUtf8.size(), fParagraphStyle.getTextStyle());
+}
+
+void ParagraphBuilderImpl::pushStyle(const TextStyle& style) {
+    this->endRunIfNeeded();
+
+    fTextStyles.push(style);
+    if (!fStyledBlocks.empty() && fStyledBlocks.back().fEnd == fUtf8.size() &&
+        fStyledBlocks.back().fStyle == style) {
+        // Just continue with the same style
+    } else {
+        // Go with the new style
+        fStyledBlocks.emplace_back(fUtf8.size(), fUtf8.size(), fTextStyles.top());
+    }
+}
+
+void ParagraphBuilderImpl::pop() {
+    this->endRunIfNeeded();
+
+    if (fTextStyles.size() > 1) {
+        fTextStyles.pop();
+    } else {
+        // In this case we use paragraph style and skip Pop operation
+        SkDebugf("SkParagraphBuilder.Pop() called too many times.\n");
+    }
+
+    auto top = fTextStyles.top();
+    fStyledBlocks.emplace_back(fUtf8.size(), fUtf8.size(), top);
+}
+
+TextStyle ParagraphBuilderImpl::peekStyle() {
+    this->endRunIfNeeded();
+
+    if (!fTextStyles.empty()) {
+        return fTextStyles.top();
+    } else {
+        SkDebugf("SkParagraphBuilder._styles is empty.\n");
+        return fParagraphStyle.getTextStyle();
+    }
+}
+
+void ParagraphBuilderImpl::addText(const std::u16string& text) {
+    icu::UnicodeString unicode;
+    unicode.setTo((UChar*)text.data());
+    std::string str;
+    unicode.toUTF8String(str);
+    // SkDebugf("Layout text16: '%s'\n", str.c_str());
+    fUtf8.insert(fUtf8.size(), str.c_str());
+}
+
+void ParagraphBuilderImpl::addText(const char* text) {
+    // SkDebugf("Layout text8: '%s'\n", text);
+    fUtf8.insert(fUtf8.size(), text);
+}
+
+void ParagraphBuilderImpl::endRunIfNeeded() {
+    if (fStyledBlocks.empty()) {
+        return;
+    }
+
+    auto& last = fStyledBlocks.back();
+    if (last.fStart == fUtf8.size()) {
+        fStyledBlocks.pop_back();
+    } else {
+        last.fEnd = fUtf8.size();
+    }
+}
+
+std::unique_ptr<Paragraph> ParagraphBuilderImpl::Build() {
+    if (!fUtf8.isEmpty()) {
+        this->endRunIfNeeded();
+    }
+    return skstd::make_unique<ParagraphImpl>(
+            fUtf8, fParagraphStyle, fStyledBlocks, fFontCollection);
+}
+
+}  // namespace textlayout
+}  // namespace skia
diff --git a/modules/skparagraph/src/ParagraphBuilderImpl.h b/modules/skparagraph/src/ParagraphBuilderImpl.h
new file mode 100644
index 0000000..ca5ddde
--- /dev/null
+++ b/modules/skparagraph/src/ParagraphBuilderImpl.h
@@ -0,0 +1,67 @@
+// Copyright 2019 Google LLC.
+#ifndef ParagraphBuilderImpl_DEFINED
+#define ParagraphBuilderImpl_DEFINED
+
+#include <memory>
+#include <stack>
+#include <string>
+#include <tuple>
+#include "modules/skparagraph/include/FontCollection.h"
+#include "modules/skparagraph/include/Paragraph.h"
+#include "modules/skparagraph/include/ParagraphBuilder.h"
+#include "modules/skparagraph/include/ParagraphStyle.h"
+#include "modules/skparagraph/include/TextStyle.h"
+
+namespace skia {
+namespace textlayout {
+
+class ParagraphBuilderImpl : public ParagraphBuilder {
+public:
+    ParagraphBuilderImpl(ParagraphStyle style, sk_sp<FontCollection> fontCollection);
+
+    ~ParagraphBuilderImpl() override;
+
+    // Push a style to the stack. The corresponding text added with AddText will
+    // use the top-most style.
+    void pushStyle(const TextStyle& style) override;
+
+    // Remove a style from the stack. Useful to apply different styles to chunks
+    // of text such as bolding.
+    // Example:
+    //   builder.PushStyle(normal_style);
+    //   builder.AddText("Hello this is normal. ");
+    //
+    //   builder.PushStyle(bold_style);
+    //   builder.AddText("And this is BOLD. ");
+    //
+    //   builder.Pop();
+    //   builder.AddText(" Back to normal again.");
+    void pop() override;
+
+    TextStyle peekStyle() override;
+
+    // Adds text to the builder. Forms the proper runs to use the upper-most style
+    // on the style_stack_;
+    void addText(const std::u16string& text) override;
+
+    // Converts to u16string before adding.
+    void addText(const char* text) override;
+
+    void setParagraphStyle(const ParagraphStyle& style) override;
+
+    // Constructs a SkParagraph object that can be used to layout and paint the text to a SkCanvas.
+    std::unique_ptr<Paragraph> Build() override;
+
+private:
+    void endRunIfNeeded();
+
+    SkString fUtf8;
+    std::stack<TextStyle> fTextStyles;
+    std::vector<Block> fStyledBlocks;
+    sk_sp<FontCollection> fFontCollection;
+    ParagraphStyle fParagraphStyle;
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // ParagraphBuilderImpl_DEFINED
diff --git a/modules/skparagraph/src/ParagraphCache.h b/modules/skparagraph/src/ParagraphCache.h
new file mode 100644
index 0000000..cc3f9d5
--- /dev/null
+++ b/modules/skparagraph/src/ParagraphCache.h
@@ -0,0 +1,96 @@
+// Copyright 2019 Google LLC.
+#ifndef ParagraphCache_DEFINED
+#define ParagraphCache_DEFINED
+
+namespace skia {
+namespace textlayout {
+
+// Just the flutter input for now
+class ParagraphCacheKey {
+public:
+    ParagraphCacheKey(SkParagraphStyle paraStyle,
+                      SkTHashMap<const char*, std::pair<SkFont, SkScalar>> mapping,
+                      SkSpan<const char> utf8)
+            : fHash(0) {
+        fHash = mix(fHash, paraStyle.computeHash());
+        fHash = mix(fHash, computeHash(mapping));
+        fHash = mix(fHash, computeHash(utf8));
+    }
+
+    uint32_t hash() const { return fHash; }
+
+private:
+    uint32 computeHash(SkTHashMap<const char*, std::pair<SkFont, SkScalar>> mapping) {
+        uint32 hash = 0;
+        mapping.forEach([&hash](const char* ch, std::pair<SkFont, SkScalar> font) {
+            hash = mix(hash, t.computeHash());
+        });
+        for (auto& t : array) {
+            hash = mix(hash, ch);
+            hash = mix(hash, SkGoodHash(font.first));
+            hash = mix(hash, SkGoodHash(font.second));
+        }
+        return hash;
+    }
+
+    uint32 computeHash(SkSpan<const char> text) {}
+
+    uint32 computeHash(SkSpan<const char> text) {
+        uint32 hash = mix(0, text.size());
+        for (uint32 i = 0; i < text.size(); i += 2) {
+            uint32 data = text[i] | text[i + 1] << 16;
+            hash = mix(hash, data);
+        }
+        if (text.size() & 1) {
+            uint32 data = text.back();
+            hash = mix(hash, data);
+        }
+        return hash;
+    }
+
+    uint32 mix(uint32 hash, uint32 data) {
+        hash += data;
+        hash += (hash << 10);
+        hash ^= (hash >> 6);
+        return hash;
+    }
+
+    uint32 fHash;
+};
+
+class ParagraphCacheValue {
+public:
+    ParagraphCacheValue(std::shared<SkParagraph> paragraph,
+                        SkTHashMap<const char*,
+                        std::pair<SkFont, SkScalar>> mapping)
+        : fKey(ParagraphCacheKey(paragraph.getParagraphStyle(), mapping, paragraph.getText()))
+        , fFontCollection(collection)
+        , fParagraphStyle(paraStyle)
+        , fTextStyles(textStyles)
+        , fUtf8(utf8) {}
+
+    static const ParagraphCacheKey& GetKey(const ParagraphCacheValue& value) { return fKey; }
+    static uint32_t Hash(const ParagraphCacheKey& key) { return fKey.hash(); }
+
+private:
+    ParagraphCacheKey fKey;
+
+    std::shared<SkParagraph> fParagraph;
+    std::pair<SkFont, SkScalar>>fMapping;
+};
+
+class ParagraphCache : public SkTDynamicHash<ParagraphCacheValue, ParagraphCacheKey> {
+public:
+    Hash() : INHERITED() {}
+
+    // Promote protected methods to public for this test.
+    int capacity() const { return this->INHERITED::capacity(); }
+    int countCollisions(const int& key) const { return this->INHERITED::countCollisions(key); }
+
+private:
+    typedef SkTDynamicHash<ParagraphCacheValue, ParagraphCacheKey> INHERITED;
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // ParagraphCache_DEFINED
diff --git a/modules/skparagraph/src/ParagraphImpl.cpp b/modules/skparagraph/src/ParagraphImpl.cpp
new file mode 100644
index 0000000..25b944a
--- /dev/null
+++ b/modules/skparagraph/src/ParagraphImpl.cpp
@@ -0,0 +1,623 @@
+// Copyright 2019 Google LLC.
+#include "modules/skparagraph/src/ParagraphImpl.h"
+#include <unicode/brkiter.h>
+#include <unicode/ubidi.h>
+#include <unicode/unistr.h>
+#include "include/core/SkBlurTypes.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkFontMgr.h"
+#include "include/core/SkPictureRecorder.h"
+#include "modules/skparagraph/src/FontIterator.h"
+#include "modules/skparagraph/src/Run.h"
+#include "modules/skparagraph/src/TextWrapper.h"
+#include "src/core/SkSpan.h"
+#include "src/utils/SkUTF.h"
+
+namespace {
+
+SkSpan<const char> operator*(const SkSpan<const char>& a, const SkSpan<const char>& b) {
+    auto begin = SkTMax(a.begin(), b.begin());
+    auto end = SkTMin(a.end(), b.end());
+    return SkSpan<const char>(begin, end > begin ? end - begin : 0);
+}
+
+SkUnichar utf8_next(const char** ptr, const char* end) {
+    SkUnichar val = SkUTF::NextUTF8(ptr, end);
+    return val < 0 ? 0xFFFD : val;
+}
+
+class TextBreaker {
+public:
+    TextBreaker() : fPos(-1) {}
+
+    bool initialize(SkSpan<const char> text, UBreakIteratorType type) {
+        UErrorCode status = U_ZERO_ERROR;
+
+        fSize = text.size();
+        UText utf8UText = UTEXT_INITIALIZER;
+        utext_openUTF8(&utf8UText, text.begin(), text.size(), &status);
+        fAutoClose =
+                std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>>(&utf8UText);
+        if (U_FAILURE(status)) {
+            SkDebugf("Could not create utf8UText: %s", u_errorName(status));
+            return false;
+        }
+        fIterator = ubrk_open(type, "en", nullptr, 0, &status);
+        if (U_FAILURE(status)) {
+            SkDebugf("Could not create line break iterator: %s", u_errorName(status));
+            SK_ABORT("");
+        }
+
+        ubrk_setUText(fIterator, &utf8UText, &status);
+        if (U_FAILURE(status)) {
+            SkDebugf("Could not setText on break iterator: %s", u_errorName(status));
+            return false;
+        }
+
+        fPos = 0;
+        return true;
+    }
+
+    size_t first() {
+        fPos = ubrk_first(fIterator);
+        return eof() ? fSize : fPos;
+    }
+
+    size_t next() {
+        fPos = ubrk_next(fIterator);
+        return eof() ? fSize : fPos;
+    }
+
+    int32_t status() { return ubrk_getRuleStatus(fIterator); }
+
+    bool eof() { return fPos == icu::BreakIterator::DONE; }
+
+    ~TextBreaker() = default;
+
+private:
+    std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>> fAutoClose;
+    UBreakIterator* fIterator;
+    int32_t fPos;
+    size_t fSize;
+};
+}  // namespace
+
+namespace skia {
+namespace textlayout {
+
+ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
+                             ParagraphStyle style,
+                             std::vector<Block>
+                                     blocks,
+                             sk_sp<FontCollection>
+                                     fonts)
+        : Paragraph(std::move(style), std::move(fonts)), fPicture(nullptr) {
+    icu::UnicodeString unicode((UChar*)utf16text.data(), SkToS32(utf16text.size()));
+    std::string str;
+    unicode.toUTF8String(str);
+    fText = SkString(str.data(), str.size());
+    fTextSpan = SkSpan<const char>(fText.c_str(), fText.size());
+
+    fTextStyles.reserve(blocks.size());
+    for (auto& block : blocks) {
+        fTextStyles.emplace_back(
+                SkSpan<const char>(fTextSpan.begin() + block.fStart, block.fEnd - block.fStart),
+                block.fStyle);
+    }
+}
+
+ParagraphImpl::~ParagraphImpl() = default;
+
+void ParagraphImpl::layout(SkScalar width) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    this->resetContext();
+
+    this->resolveStrut();
+
+    if (!this->shapeTextIntoEndlessLine()) {
+        // Apply the last style to the empty text
+        FontIterator font(SkSpan<const char>(" "),
+                          SkSpan<TextBlock>(&fTextStyles.back(), 1),
+                          fFontCollection,
+                          fParagraphStyle.hintingIsOn());
+        font.consume();
+        SkFontMetrics metrics;
+        font.currentFont().getMetrics(&metrics);
+        fHeight = LineMetrics(metrics).height();
+        return;
+    }
+
+    this->buildClusterTable();
+
+    this->breakShapedTextIntoLines(width);
+}
+
+void ParagraphImpl::resolveStrut() {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    auto strutStyle = this->paragraphStyle().getStrutStyle();
+    if (!strutStyle.getStrutEnabled()) {
+        return;
+    }
+
+    sk_sp<SkTypeface> typeface;
+    for (auto& fontFamily : strutStyle.getFontFamilies()) {
+        typeface = fFontCollection->matchTypeface(fontFamily.c_str(), strutStyle.getFontStyle());
+        if (typeface.get() != nullptr) {
+            break;
+        }
+    }
+    if (typeface.get() == nullptr) {
+        typeface = SkTypeface::MakeDefault();
+    }
+
+    SkFont font(typeface, strutStyle.getFontSize());
+    SkFontMetrics metrics;
+    font.getMetrics(&metrics);
+
+    fStrutMetrics = LineMetrics(metrics.fAscent * strutStyle.getHeight(),
+                                metrics.fDescent * strutStyle.getHeight(),
+                                strutStyle.getLeading() < 0
+                                        ? metrics.fLeading
+                                        : strutStyle.getLeading() * strutStyle.getFontSize());
+}
+
+void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    if (nullptr == fPicture) {
+        // Build the picture lazily not until we actually have to paint (or never)
+        this->formatLines(fWidth);
+        this->paintLinesIntoPicture();
+    }
+
+    SkMatrix matrix = SkMatrix::MakeTrans(x, y);
+    canvas->drawPicture(fPicture, &matrix, nullptr);
+}
+
+void ParagraphImpl::resetContext() {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    fAlphabeticBaseline = 0;
+    fHeight = 0;
+    fWidth = 0;
+    fIdeographicBaseline = 0;
+    fMaxIntrinsicWidth = 0;
+    fMinIntrinsicWidth = 0;
+    fMaxLineWidth = 0;
+
+    fPicture = nullptr;
+    fRuns.reset();
+    fClusters.reset();
+    fLines.reset();
+}
+
+// Clusters in the order of the input text
+void ParagraphImpl::buildClusterTable() {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    // Find all possible (soft) line breaks
+    TextBreaker breaker;
+    if (!breaker.initialize(fTextSpan, UBRK_LINE)) {
+        return;
+    }
+    size_t currentPos = breaker.first();
+    SkTHashMap<const char*, bool> softLineBreaks;
+    while (!breaker.eof()) {
+        currentPos = breaker.next();
+        const char* ch = currentPos + fTextSpan.begin();
+        softLineBreaks.set(ch, breaker.status() == UBRK_LINE_HARD);
+    }
+
+    TextBlock* currentStyle = this->fTextStyles.begin();
+    SkScalar shift = 0;
+    // Cannot set SkSpan<SkCluster> until the array is done - can be moved around
+    std::vector<std::tuple<Run*, size_t, size_t>> toUpdate;
+
+    // Walk through all the run in the direction of input text
+    for (auto& run : fRuns) {
+        auto runStart = fClusters.size();
+        // Walk through the glyph in the direction of input text
+        run.iterateThroughClustersInTextOrder([&run, this, &softLineBreaks, &currentStyle, &shift](
+                                                      size_t glyphStart,
+                                                      size_t glyphEnd,
+                                                      size_t charStart,
+                                                      size_t charEnd,
+                                                      SkScalar width,
+                                                      SkScalar height) {
+            SkASSERT(charEnd >= charStart);
+            SkSpan<const char> text(fTextSpan.begin() + charStart, charEnd - charStart);
+
+            auto& cluster = fClusters.emplace_back(&run, glyphStart, glyphEnd, text, width, height);
+
+            // Mark the line breaks
+            auto found = softLineBreaks.find(cluster.text().end());
+            if (found) {
+                cluster.setBreakType(*found ? Cluster::BreakType::HardLineBreak
+                                            : Cluster::BreakType::SoftLineBreak);
+            }
+            cluster.setIsWhiteSpaces();
+
+            // Shift the cluster
+            run.shift(&cluster, shift);
+
+            // Synchronize styles (one cluster can be covered by few styles)
+            while (!cluster.startsIn(currentStyle->text())) {
+                currentStyle++;
+                SkASSERT(currentStyle != this->fTextStyles.end());
+            }
+
+            // Take spacing styles in account
+            if (currentStyle->style().getWordSpacing() != 0 &&
+                fParagraphStyle.getTextAlign() != TextAlign::kJustify) {
+                if (cluster.isWhitespaces() && cluster.isSoftBreak()) {
+                    shift +=
+                            run.addSpacesAtTheEnd(currentStyle->style().getWordSpacing(), &cluster);
+                }
+            }
+            if (currentStyle->style().getLetterSpacing() != 0) {
+                shift += run.addSpacesEvenly(currentStyle->style().getLetterSpacing(), &cluster);
+            }
+        });
+
+        toUpdate.emplace_back(&run, runStart, fClusters.size() - runStart);
+        fMaxIntrinsicWidth += run.advance().fX;
+    }
+    fClusters.emplace_back(nullptr, 0, 0, SkSpan<const char>(), 0, 0);
+
+    // Set SkSpan<SkCluster> ranges for all the runs
+    for (auto update : toUpdate) {
+        auto run = std::get<0>(update);
+        auto start = std::get<1>(update);
+        auto size = std::get<2>(update);
+        run->setClusters(SkSpan<Cluster>(&fClusters[start], size));
+    }
+}
+
+bool ParagraphImpl::shapeTextIntoEndlessLine() {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    class ShapeHandler final : public SkShaper::RunHandler {
+    public:
+        explicit ShapeHandler(ParagraphImpl& paragraph, FontIterator* fontIterator)
+                : fParagraph(&paragraph)
+                , fFontIterator(fontIterator)
+                , fAdvance(SkVector::Make(0, 0)) {}
+
+        SkVector advance() const { return fAdvance; }
+
+    private:
+        void beginLine() override {}
+
+        void runInfo(const RunInfo&) override {}
+
+        void commitRunInfo() override {}
+
+        Buffer runBuffer(const RunInfo& info) override {
+            TRACE_EVENT0("skia", TRACE_FUNC);
+            auto& run = fParagraph->fRuns.emplace_back(fParagraph->text(),
+                                                       info,
+                                                       fFontIterator->lineHeight(),
+                                                       fParagraph->fRuns.count(),
+                                                       fAdvance.fX);
+            return run.newRunBuffer();
+        }
+
+        void commitRunBuffer(const RunInfo&) override {
+            TRACE_EVENT0("skia", TRACE_FUNC);
+            auto& run = fParagraph->fRuns.back();
+            if (run.size() == 0) {
+                fParagraph->fRuns.pop_back();
+                return;
+            }
+            // Carve out the line text out of the entire run text
+            fAdvance.fX += run.advance().fX;
+            fAdvance.fY = SkMaxScalar(fAdvance.fY, run.descent() - run.ascent());
+        }
+
+        void commitLine() override {}
+
+        ParagraphImpl* fParagraph;
+        FontIterator* fFontIterator;
+        SkVector fAdvance;
+    };
+
+    if (fTextSpan.empty()) {
+        return false;
+    }
+
+    SkSpan<TextBlock> styles(fTextStyles.begin(), fTextStyles.size());
+    FontIterator font(fTextSpan, styles, fFontCollection, fParagraphStyle.hintingIsOn());
+    ShapeHandler handler(*this, &font);
+    std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
+    SkASSERT_RELEASE(shaper != nullptr);
+    auto bidi = SkShaper::MakeIcuBiDiRunIterator(
+            fTextSpan.begin(), fTextSpan.size(),
+            fParagraphStyle.getTextDirection() == TextDirection::kLtr ? (uint8_t)2 : (uint8_t)1);
+    if (bidi == nullptr) {
+        return false;
+    }
+    auto script = SkShaper::MakeHbIcuScriptRunIterator(fTextSpan.begin(), fTextSpan.size());
+    auto lang = SkShaper::MakeStdLanguageRunIterator(fTextSpan.begin(), fTextSpan.size());
+
+    shaper->shape(fTextSpan.begin(), fTextSpan.size(), font, *bidi, *script, *lang,
+                  std::numeric_limits<SkScalar>::max(), &handler);
+    return true;
+}
+
+void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    TextWrapper textWrapper;
+    textWrapper.breakTextIntoLines(
+            this,
+            maxWidth,
+            [&](SkSpan<const char> text,
+                SkSpan<const char>
+                        textWithSpaces,
+                Cluster* start,
+                Cluster* end,
+                size_t startPos,
+                size_t endPos,
+                SkVector offset,
+                SkVector advance,
+                LineMetrics metrics,
+                bool addEllipsis) {
+                // Add the line
+                // TODO: Take in account clipped edges
+                SkSpan<const Cluster> clusters(start, end - start + 1);
+                auto& line = this->addLine(offset, advance, text, textWithSpaces, clusters,
+                                           startPos, endPos, metrics);
+                if (addEllipsis) {
+                    line.createEllipsis(maxWidth, fParagraphStyle.getEllipsis(), true);
+                }
+            });
+
+    fHeight = textWrapper.height();
+    fWidth = maxWidth;  // fTextWrapper.width();
+    fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
+    fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
+    fAlphabeticBaseline = fLines.empty() ? 0 : fLines.front().alphabeticBaseline();
+    fIdeographicBaseline = fLines.empty() ? 0 : fLines.front().ideographicBaseline();
+}
+
+void ParagraphImpl::formatLines(SkScalar maxWidth) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    auto effectiveAlign = fParagraphStyle.effective_align();
+    for (auto& line : fLines) {
+        line.format(effectiveAlign, maxWidth, &line != &fLines.back());
+    }
+}
+
+void ParagraphImpl::paintLinesIntoPicture() {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    SkPictureRecorder recorder;
+    SkCanvas* textCanvas = recorder.beginRecording(fWidth, fHeight, nullptr, 0);
+
+    for (auto& line : fLines) {
+        line.paint(textCanvas);
+    }
+
+    fPicture = recorder.finishRecordingAsPicture();
+}
+
+SkSpan<const TextBlock> ParagraphImpl::findAllBlocks(SkSpan<const char> text) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    const TextBlock* begin = nullptr;
+    const TextBlock* end = nullptr;
+    for (auto& block : fTextStyles) {
+        if (block.text().end() <= text.begin()) {
+            continue;
+        }
+        if (block.text().begin() >= text.end()) {
+            break;
+        }
+        if (begin == nullptr) {
+            begin = &block;
+        }
+        end = &block;
+    }
+
+    return SkSpan<const TextBlock>(begin, end - begin + 1);
+}
+
+TextLine& ParagraphImpl::addLine(SkVector offset,
+                                 SkVector advance,
+                                 SkSpan<const char> text,
+                                 SkSpan<const char> textWithSpaces,
+                                 SkSpan<const Cluster> clusters,
+                                 size_t start,
+                                 size_t end,
+                                 LineMetrics sizes) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    // Define a list of styles that covers the line
+    auto blocks = findAllBlocks(text);
+
+    return fLines.emplace_back(offset, advance, blocks, text, textWithSpaces, clusters, start, end,
+                               sizes);
+}
+
+// Returns a vector of bounding boxes that enclose all text between
+// start and end glyph indexes, including start and excluding end
+std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
+                                                     unsigned end,
+                                                     RectHeightStyle rectHeightStyle,
+                                                     RectWidthStyle rectWidthStyle) {
+    std::vector<TextBox> results;
+    if (start >= end || start > fTextSpan.size() || end == 0) {
+        return results;
+    }
+
+    // Calculate the utf8 substring
+    const char* first = fTextSpan.begin();
+    for (unsigned i = 0; i < start; ++i) {
+        utf8_next(&first, fTextSpan.end());
+    }
+    const char* last = first;
+    for (unsigned i = start; i < end; ++i) {
+        utf8_next(&last, fTextSpan.end());
+    }
+    SkSpan<const char> text(first, last - first);
+
+    for (auto& line : fLines) {
+        auto lineText = line.textWithSpaces();
+        auto intersect = lineText * text;
+        if (intersect.empty() && (!lineText.empty() || lineText.begin() != text.begin())) {
+            continue;
+        }
+
+        SkScalar runOffset = 0;
+        if (lineText.begin() != intersect.begin()) {
+            SkSpan<const char> before(lineText.begin(), intersect.begin() - lineText.begin());
+            runOffset = line.iterateThroughRuns(
+                    before, 0, true,
+                    [](Run*, size_t, size_t, SkRect, SkScalar, bool) { return true; });
+        }
+        auto firstBox = results.size();
+        line.iterateThroughRuns(intersect,
+                                runOffset,
+                                true,
+                                [&results, &line](Run* run, size_t pos, size_t size, SkRect clip,
+                                                  SkScalar shift, bool clippingNeeded) {
+                                    clip.offset(line.offset());
+                                    results.emplace_back(clip, run->leftToRight()
+                                                                       ? TextDirection::kLtr
+                                                                       : TextDirection::kRtl);
+                                    return true;
+                                });
+
+        if (rectHeightStyle != RectHeightStyle::kTight) {
+            // Align all the rectangles
+            for (auto i = firstBox; i < results.size(); ++i) {
+                auto& rect = results[i].rect;
+                if (rectHeightStyle == RectHeightStyle::kMax) {
+                    rect.fTop = line.offset().fY + line.roundingDelta();
+                    rect.fBottom = line.offset().fY + line.height();
+
+                } else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingTop) {
+                    rect.fTop = line.offset().fY;
+
+                } else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingMiddle) {
+                    rect.fTop -= (rect.fTop - line.offset().fY) / 2;
+                    rect.fBottom += (line.offset().fY + line.height() - rect.fBottom) / 2;
+
+                } else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingBottom) {
+                    rect.fBottom = line.offset().fY + line.height();
+                }
+            }
+        } else {
+            // Just leave the boxes the way they are
+        }
+
+        if (rectWidthStyle == RectWidthStyle::kMax) {
+            for (auto& i = firstBox; i < results.size(); ++i) {
+                auto clip = results[i].rect;
+                auto dir = results[i].direction;
+                if (clip.fLeft > line.offset().fX) {
+                    SkRect left = SkRect::MakeXYWH(0, clip.fTop, clip.fLeft - line.offset().fX,
+                                                   clip.fBottom);
+                    results.insert(results.begin() + i, {left, dir});
+                    ++i;
+                }
+                if (clip.fRight < line.offset().fX + line.width()) {
+                    SkRect right = SkRect::MakeXYWH(clip.fRight - line.offset().fX,
+                                                    clip.fTop,
+                                                    line.width() - (clip.fRight - line.offset().fX),
+                                                    clip.fBottom);
+                    results.insert(results.begin() + i, {right, dir});
+                    ++i;
+                }
+            }
+        }
+    }
+
+    return results;
+}
+// TODO: Deal with RTL here
+PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
+    PositionWithAffinity result(0, Affinity::kDownstream);
+    for (auto& line : fLines) {
+        // This is so far the the line vertically closest to our coordinates
+        // (or the first one, or the only one - all the same)
+        line.iterateThroughRuns(
+                line.textWithSpaces(),
+                0,
+                true,
+                [dx, &result](Run* run, size_t pos, size_t size, SkRect clip, SkScalar shift,
+                              bool clippingNeeded) {
+                    if (dx < clip.fLeft) {
+                        // All the other runs are placed right of this one
+                        result = {SkToS32(run->fClusterIndexes[pos]), kDownstream};
+                        return false;
+                    }
+
+                    if (dx >= clip.fRight) {
+                        // We have to keep looking but just in case keep the last one as the closes
+                        // so far
+                        result = {SkToS32(run->fClusterIndexes[pos + size]), kUpstream};
+                        return true;
+                    }
+
+                    // So we found the run that contains our coordinates
+                    size_t found = pos;
+                    for (size_t i = pos; i < pos + size; ++i) {
+                        if (run->positionX(i) + shift > dx) {
+                            break;
+                        }
+                        found = i;
+                    }
+
+                    if (found == pos) {
+                        result = {SkToS32(run->fClusterIndexes[found]), kDownstream};
+                    } else if (found == pos + size - 1) {
+                        result = {SkToS32(run->fClusterIndexes[found]), kUpstream};
+                    } else {
+                        auto center = (run->positionX(found + 1) + run->positionX(found)) / 2;
+                        if ((dx <= center + shift) == run->leftToRight()) {
+                            result = {SkToS32(run->fClusterIndexes[found]), kDownstream};
+                        } else {
+                            result = {SkToS32(run->fClusterIndexes[found + 1]), kUpstream};
+                        }
+                    }
+                    // No need to continue
+                    return false;
+                });
+
+        // Let's figure out if we can stop looking
+        auto offsetY = line.offset().fY;
+        if (dy < offsetY) {
+            // The closest position on this line; next line is going to be even lower
+            break;
+        }
+        if (dy >= offsetY + line.height()) {
+            // We have the closest position on the lowest line so far, but we have to continue
+            continue;
+        }
+
+        // We hit the line; nothing else to do
+        break;
+    }
+
+    // SkDebugf("getGlyphPositionAtCoordinate(%f,%f) = %d\n", dx, dy, result.position);
+    return result;
+}
+
+// Finds the first and last glyphs that define a word containing
+// the glyph at index offset.
+// By "glyph" they mean a character index - indicated by Minikin's code
+SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
+    TextBreaker breaker;
+    if (!breaker.initialize(fTextSpan, UBRK_WORD)) {
+        return {0, 0};
+    }
+
+    size_t currentPos = breaker.first();
+    while (true) {
+        auto start = currentPos;
+        currentPos = breaker.next();
+        if (breaker.eof()) {
+            break;
+        }
+        if (start <= offset && currentPos > offset) {
+            return {start, currentPos};
+        }
+    }
+    return {0, 0};
+}
+
+}  // namespace textlayout
+}  // namespace skia
diff --git a/modules/skparagraph/src/ParagraphImpl.h b/modules/skparagraph/src/ParagraphImpl.h
new file mode 100644
index 0000000..fca49ee
--- /dev/null
+++ b/modules/skparagraph/src/ParagraphImpl.h
@@ -0,0 +1,113 @@
+// Copyright 2019 Google LLC.
+#ifndef ParagraphImpl_DEFINED
+#define ParagraphImpl_DEFINED
+
+#include "modules/skparagraph/src/TextLine.h"
+#include "modules/skparagraph/src/Run.h"
+#include "include/core/SkPicture.h"
+#include "include/private//SkTHash.h"
+#include "modules/skparagraph/include/Paragraph.h"
+#include "modules/skparagraph/include/ParagraphStyle.h"
+#include "modules/skparagraph/include/TextStyle.h"
+
+class SkCanvas;
+
+namespace skia {
+namespace textlayout {
+
+template <typename T> bool operator==(const SkSpan<T>& a, const SkSpan<T>& b) {
+    return a.size() == b.size() && a.begin() == b.begin();
+}
+
+template <typename T> bool operator<=(const SkSpan<T>& a, const SkSpan<T>& b) {
+    return a.begin() >= b.begin() && a.end() <= b.end();
+}
+
+class ParagraphImpl final : public Paragraph {
+public:
+    ParagraphImpl(const SkString& text,
+                  ParagraphStyle style,
+                  std::vector<Block> blocks,
+                  sk_sp<FontCollection> fonts)
+            : Paragraph(std::move(style), std::move(fonts))
+            , fText(text)
+            , fTextSpan(fText.c_str(), fText.size())
+            , fPicture(nullptr) {
+        fTextStyles.reserve(blocks.size());
+        for (auto& block : blocks) {
+            fTextStyles.emplace_back(
+                    SkSpan<const char>(fTextSpan.begin() + block.fStart, block.fEnd - block.fStart),
+                    block.fStyle);
+        }
+    }
+
+    ParagraphImpl(const std::u16string& utf16text,
+                    ParagraphStyle style,
+                    std::vector<Block> blocks,
+                    sk_sp<FontCollection> fonts);
+    ~ParagraphImpl() override;
+
+    void layout(SkScalar width) override;
+    void paint(SkCanvas* canvas, SkScalar x, SkScalar y) override;
+    std::vector<TextBox> getRectsForRange(unsigned start,
+                                          unsigned end,
+                                          RectHeightStyle rectHeightStyle,
+                                          RectWidthStyle rectWidthStyle) override;
+    PositionWithAffinity getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) override;
+    SkRange<size_t> getWordBoundary(unsigned offset) override;
+    bool didExceedMaxLines() override {
+        return !fParagraphStyle.unlimited_lines() && fLines.size() > fParagraphStyle.getMaxLines();
+    }
+
+    size_t lineNumber() override { return fLines.size(); }
+
+    TextLine& addLine(SkVector offset, SkVector advance, SkSpan<const char> text,
+                      SkSpan<const char> textWithSpaces, SkSpan<const Cluster> clusters,
+                      size_t start, size_t end, LineMetrics sizes);
+
+    SkSpan<const char> text() const { return fTextSpan; }
+    SkSpan<Run> runs() { return SkSpan<Run>(fRuns.data(), fRuns.size()); }
+    SkSpan<TextBlock> styles() {
+        return SkSpan<TextBlock>(fTextStyles.data(), fTextStyles.size());
+    }
+    SkSpan<TextLine> lines() { return SkSpan<TextLine>(fLines.data(), fLines.size()); }
+    ParagraphStyle paragraphStyle() const { return fParagraphStyle; }
+    SkSpan<Cluster> clusters() { return SkSpan<Cluster>(fClusters.begin(), fClusters.size()); }
+    void formatLines(SkScalar maxWidth);
+
+    bool strutEnabled() const { return paragraphStyle().getStrutStyle().getStrutEnabled(); }
+    bool strutForceHeight() const {
+        return paragraphStyle().getStrutStyle().getForceStrutHeight();
+    }
+    LineMetrics strutMetrics() const { return fStrutMetrics; }
+
+private:
+    friend class ParagraphBuilder;
+
+    void resetContext();
+    void resolveStrut();
+    void buildClusterTable();
+    bool shapeTextIntoEndlessLine();
+    void breakShapedTextIntoLines(SkScalar maxWidth);
+    void paintLinesIntoPicture();
+
+    SkSpan<const TextBlock> findAllBlocks(SkSpan<const char> text);
+
+    // Input
+    SkTArray<TextBlock, true> fTextStyles;
+    SkString fText;
+    SkSpan<const char> fTextSpan;
+
+    // Internal structures
+    SkTArray<Run> fRuns;
+    SkTArray<Cluster, true> fClusters;
+    SkTArray<TextLine> fLines;
+    LineMetrics fStrutMetrics;
+
+    // Painting
+    sk_sp<SkPicture> fPicture;
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // ParagraphImpl_DEFINED
diff --git a/modules/skparagraph/src/ParagraphStyle.cpp b/modules/skparagraph/src/ParagraphStyle.cpp
new file mode 100644
index 0000000..c2f238a
--- /dev/null
+++ b/modules/skparagraph/src/ParagraphStyle.cpp
@@ -0,0 +1,44 @@
+// Copyright 2019 Google LLC.
+#include <string>
+#include "modules/skparagraph/include/ParagraphStyle.h"
+#include "unicode/unistr.h"
+
+namespace skia {
+namespace textlayout {
+
+StrutStyle::StrutStyle() {
+    fFontStyle = SkFontStyle::Normal();
+    fFontSize = 14;
+    fHeight = 1;
+    fLeading = -1;
+    fForceStrutHeight = false;
+    fStrutEnabled = false;
+}
+
+ParagraphStyle::ParagraphStyle() {
+    fTextAlign = TextAlign::kStart;
+    fTextDirection = TextDirection::kLtr;
+    fLinesLimit = std::numeric_limits<size_t>::max();
+    fHeight = 1;
+    fHintingIsOn = true;
+}
+
+TextAlign ParagraphStyle::effective_align() const {
+    if (fTextAlign == TextAlign::kStart) {
+        return (fTextDirection == TextDirection::kLtr) ? TextAlign::kLeft : TextAlign::kRight;
+    } else if (fTextAlign == TextAlign::kEnd) {
+        return (fTextDirection == TextDirection::kLtr) ? TextAlign::kRight : TextAlign::kLeft;
+    } else {
+        return fTextAlign;
+    }
+}
+
+void ParagraphStyle::setEllipsis(const std::u16string& ellipsis) {
+    icu::UnicodeString unicode;
+    unicode.setTo((UChar*)ellipsis.data());
+    std::string str;
+    unicode.toUTF8String(str);
+    fEllipsis = SkString(str.c_str());
+}
+}  // namespace textlayout
+}  // namespace skia
diff --git a/modules/skparagraph/src/Run.cpp b/modules/skparagraph/src/Run.cpp
new file mode 100644
index 0000000..1773ec5
--- /dev/null
+++ b/modules/skparagraph/src/Run.cpp
@@ -0,0 +1,246 @@
+// Copyright 2019 Google LLC.
+#include "modules/skparagraph/src/Run.h"
+#include <unicode/brkiter.h>
+#include "include/core/SkFontMetrics.h"
+
+namespace skia {
+namespace textlayout {
+
+Run::Run(SkSpan<const char> text,
+         const SkShaper::RunHandler::RunInfo& info,
+         SkScalar lineHeight,
+         size_t index,
+         SkScalar offsetX) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    fFont = info.fFont;
+    fHeightMultiplier = lineHeight;
+    fBidiLevel = info.fBidiLevel;
+    fAdvance = info.fAdvance;
+    fText = SkSpan<const char>(text.begin() + info.utf8Range.begin(), info.utf8Range.size());
+
+    fIndex = index;
+    fUtf8Range = info.utf8Range;
+    fOffset = SkVector::Make(offsetX, 0);
+    fGlyphs.push_back_n(info.glyphCount);
+    fPositions.push_back_n(info.glyphCount + 1);
+    fOffsets.push_back_n(info.glyphCount + 1, SkScalar(0));
+    fClusterIndexes.push_back_n(info.glyphCount + 1);
+    info.fFont.getMetrics(&fFontMetrics);
+    fSpaced = false;
+    // To make edge cases easier:
+    fPositions[info.glyphCount] = fOffset + fAdvance;
+    fClusterIndexes[info.glyphCount] = info.utf8Range.end();
+}
+
+SkShaper::RunHandler::Buffer Run::newRunBuffer() {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    return {fGlyphs.data(), fPositions.data(), nullptr, fClusterIndexes.data(), fOffset};
+}
+
+SkScalar Run::calculateWidth(size_t start, size_t end, bool clip) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    SkASSERT(start <= end);
+    // clip |= end == size();  // Clip at the end of the run?
+    SkScalar offset = 0;
+    if (fSpaced && end > start) {
+        offset = fOffsets[clip ? end - 1 : end] - fOffsets[start];
+    }
+    return fPositions[end].fX - fPositions[start].fX + offset;
+}
+
+void Run::copyTo(SkTextBlobBuilder& builder, size_t pos, size_t size, SkVector offset) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    SkASSERT(pos + size <= this->size());
+    const auto& blobBuffer = builder.allocRunPos(fFont, SkToInt(size));
+    sk_careful_memcpy(blobBuffer.glyphs, fGlyphs.data() + pos, size * sizeof(SkGlyphID));
+
+    if (fSpaced || offset.fX != 0 || offset.fY != 0) {
+        for (size_t i = 0; i < size; ++i) {
+            auto point = fPositions[i + pos];
+            if (fSpaced) {
+                point.fX += fOffsets[i + pos];
+            }
+            blobBuffer.points()[i] = point + offset;
+        }
+    } else {
+        // Good for the first line
+        sk_careful_memcpy(blobBuffer.points(), fPositions.data() + pos, size * sizeof(SkPoint));
+    }
+}
+
+// TODO: Make the search more effective
+std::tuple<bool, Cluster*, Cluster*> Run::findLimitingClusters(SkSpan<const char> text) {
+    if (text.empty()) {
+        Cluster* found = nullptr;
+        for (auto& cluster : fClusters) {
+            if (cluster.contains(text.begin())) {
+                found = &cluster;
+                break;
+            }
+        }
+        return std::make_tuple(found != nullptr, found, found);
+    }
+
+    auto first = text.begin();
+    auto last = text.end() - 1;
+
+    Cluster* start = nullptr;
+    Cluster* end = nullptr;
+    for (auto& cluster : fClusters) {
+        if (cluster.contains(first)) start = &cluster;
+        if (cluster.contains(last)) end = &cluster;
+    }
+    if (!leftToRight()) {
+        std::swap(start, end);
+    }
+
+    return std::make_tuple(start != nullptr && end != nullptr, start, end);
+}
+
+void Run::iterateThroughClustersInTextOrder(const ClusterVisitor& visitor) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    // Can't figure out how to do it with one code for both cases without 100 ifs
+    // Can't go through clusters because there are no cluster table yet
+    if (leftToRight()) {
+        size_t start = 0;
+        size_t cluster = this->clusterIndex(start);
+        for (size_t glyph = 1; glyph <= this->size(); ++glyph) {
+            auto nextCluster = this->clusterIndex(glyph);
+            if (nextCluster == cluster) {
+                continue;
+            }
+
+            visitor(start,
+                    glyph,
+                    cluster,
+                    nextCluster,
+                    this->calculateWidth(start, glyph, glyph == size()),
+                    this->calculateHeight());
+
+            start = glyph;
+            cluster = nextCluster;
+        }
+    } else {
+        size_t glyph = this->size();
+        size_t cluster = this->fUtf8Range.begin();
+        for (int32_t start = this->size() - 1; start >= 0; --start) {
+            size_t nextCluster =
+                    start == 0 ? this->fUtf8Range.end() : this->clusterIndex(start - 1);
+            if (nextCluster == cluster) {
+                continue;
+            }
+
+            visitor(start,
+                    glyph,
+                    cluster,
+                    nextCluster,
+                    this->calculateWidth(start, glyph, glyph == 0),
+                    this->calculateHeight());
+
+            glyph = start;
+            cluster = nextCluster;
+        }
+    }
+}
+
+SkScalar Run::addSpacesAtTheEnd(SkScalar space, Cluster* cluster) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    if (cluster->endPos() == cluster->startPos()) {
+        return 0;
+    }
+
+    fOffsets[cluster->endPos() - 1] += space;
+    // Increment the run width
+    fSpaced = true;
+    fAdvance.fX += space;
+    // Increment the cluster width
+    cluster->space(space, space);
+
+    return space;
+}
+
+SkScalar Run::addSpacesEvenly(SkScalar space, Cluster* cluster) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    // Offset all the glyphs in the cluster
+    SkScalar shift = 0;
+    for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
+        fOffsets[i] += shift;
+        shift += space;
+    }
+    if (this->size() == cluster->endPos()) {
+        // To make calculations easier
+        fOffsets[cluster->endPos()] += shift;
+    }
+    // Increment the run width
+    fSpaced = true;
+    fAdvance.fX += shift;
+    // Increment the cluster width
+    cluster->space(shift, space);
+
+    return shift;
+}
+
+void Run::shift(const Cluster* cluster, SkScalar offset) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    if (offset == 0) {
+        return;
+    }
+
+    fSpaced = true;
+    for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
+        fOffsets[i] += offset;
+    }
+    if (this->size() == cluster->endPos()) {
+        // To make calculations easier
+        fOffsets[cluster->endPos()] += offset;
+    }
+}
+
+void Cluster::setIsWhiteSpaces() {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    auto pos = fText.end();
+    while (--pos >= fText.begin()) {
+        auto ch = *pos;
+        if (!u_isspace(ch) && u_charType(ch) != U_CONTROL_CHAR &&
+            u_charType(ch) != U_NON_SPACING_MARK) {
+            return;
+        }
+    }
+    fWhiteSpaces = true;
+}
+
+SkScalar Cluster::sizeToChar(const char* ch) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    if (ch < fText.begin() || ch >= fText.end()) {
+        return 0;
+    }
+    auto shift = ch - fText.begin();
+    auto ratio = shift * 1.0 / fText.size();
+
+    return SkDoubleToScalar(fWidth * ratio);
+}
+
+SkScalar Cluster::sizeFromChar(const char* ch) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    if (ch < fText.begin() || ch >= fText.end()) {
+        return 0;
+    }
+    auto shift = fText.end() - ch - 1;
+    auto ratio = shift * 1.0 / fText.size();
+
+    return SkDoubleToScalar(fWidth * ratio);
+}
+
+size_t Cluster::roundPos(SkScalar s) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    auto ratio = (s * 1.0) / fWidth;
+    return sk_double_floor2int(ratio * size());
+}
+
+SkScalar Cluster::trimmedWidth(size_t pos) const {
+    // Find the width until the pos and return the min between trimmedWidth and the width(pos)
+    return SkTMin(this->run()->positionX(pos) - this->run()->positionX(fStart), fWidth - fSpacing);
+}
+
+}  // namespace textlayout
+}  // namespace skia
diff --git a/modules/skparagraph/src/Run.h b/modules/skparagraph/src/Run.h
new file mode 100644
index 0000000..fcaeeda
--- /dev/null
+++ b/modules/skparagraph/src/Run.h
@@ -0,0 +1,269 @@
+// Copyright 2019 Google LLC.
+#ifndef Run_DEFINED
+#define Run_DEFINED
+
+#include "include/core/SkFontMetrics.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkTextBlob.h"
+#include "modules/skshaper/include/SkShaper.h"
+#include "src/core/SkSpan.h"
+#include "src/core/SkTraceEvent.h"
+
+namespace skia {
+namespace textlayout {
+
+class Cluster;
+class Run {
+public:
+    Run() = default;
+    Run(SkSpan<const char> text,
+          const SkShaper::RunHandler::RunInfo& info,
+          SkScalar lineHeight,
+          size_t index,
+          SkScalar shiftX);
+    ~Run() {}
+
+    SkShaper::RunHandler::Buffer newRunBuffer();
+
+    size_t size() const { return fGlyphs.size(); }
+    void setWidth(SkScalar width) { fAdvance.fX = width; }
+    void setHeight(SkScalar height) { fAdvance.fY = height; }
+    void shift(SkScalar shiftX, SkScalar shiftY) {
+        fOffset.fX += shiftX;
+        fOffset.fY += shiftY;
+    }
+    SkVector advance() const {
+        return SkVector::Make(fAdvance.fX, fFontMetrics.fDescent - fFontMetrics.fAscent);
+    }
+    SkVector offset() const { return fOffset; }
+    SkScalar ascent() const { return fFontMetrics.fAscent; }
+    SkScalar descent() const { return fFontMetrics.fDescent; }
+    SkScalar leading() const { return fFontMetrics.fLeading; }
+    const SkFont& font() const { return fFont; }
+    bool leftToRight() const { return fBidiLevel % 2 == 0; }
+    size_t index() const { return fIndex; }
+    SkScalar lineHeight() const { return fHeightMultiplier; }
+    SkSpan<const char> text() const { return fText; }
+    size_t clusterIndex(size_t pos) const { return fClusterIndexes[pos]; }
+    SkScalar positionX(size_t pos) const { return fPositions[pos].fX + fOffsets[pos]; }
+    SkScalar offset(size_t index) const { return fOffsets[index]; }
+    SkSpan<Cluster> clusters() const { return fClusters; }
+    void setClusters(SkSpan<Cluster> clusters) { fClusters = clusters; }
+    SkRect clip() const {
+        return SkRect::MakeXYWH(fOffset.fX, fOffset.fY, fAdvance.fX, fAdvance.fY);
+    }
+
+    SkScalar addSpacesAtTheEnd(SkScalar space, Cluster* cluster);
+    SkScalar addSpacesEvenly(SkScalar space, Cluster* cluster);
+    void shift(const Cluster* cluster, SkScalar offset);
+
+    SkScalar calculateHeight() const { return fFontMetrics.fDescent - fFontMetrics.fAscent; }
+    SkScalar calculateWidth(size_t start, size_t end, bool clip) const;
+
+    void copyTo(SkTextBlobBuilder& builder, size_t pos, size_t size, SkVector offset) const;
+
+    using ClusterVisitor = std::function<void(size_t glyphStart,
+                                              size_t glyphEnd,
+                                              size_t charStart,
+                                              size_t charEnd,
+                                              SkScalar width,
+                                              SkScalar height)>;
+    void iterateThroughClustersInTextOrder(const ClusterVisitor& visitor);
+
+    std::tuple<bool, Cluster*, Cluster*> findLimitingClusters(SkSpan<const char> text);
+    SkSpan<const SkGlyphID> glyphs() {
+        return SkSpan<const SkGlyphID>(fGlyphs.begin(), fGlyphs.size());
+    }
+    SkSpan<const SkPoint> positions() {
+        return SkSpan<const SkPoint>(fPositions.begin(), fPositions.size());
+    }
+    SkSpan<const uint32_t> clusterIndexes() {
+        return SkSpan<const uint32_t>(fClusterIndexes.begin(), fClusterIndexes.size());
+    }
+
+private:
+    friend class ParagraphImpl;
+    friend class TextLine;
+    friend class LineMetrics;
+
+    SkFont fFont;
+    SkFontMetrics fFontMetrics;
+    SkScalar fHeightMultiplier;
+    size_t fIndex;
+    uint8_t fBidiLevel;
+    SkVector fAdvance;
+    SkSpan<const char> fText;
+    SkSpan<Cluster> fClusters;
+    SkVector fOffset;
+    SkShaper::RunHandler::Range fUtf8Range;
+    SkSTArray<128, SkGlyphID, false> fGlyphs;
+    SkSTArray<128, SkPoint, true> fPositions;
+    SkSTArray<128, uint32_t, true> fClusterIndexes;
+    SkSTArray<128, SkScalar, true> fOffsets;  // For formatting (letter/word spacing, justification)
+    bool fSpaced;
+};
+
+class Cluster {
+public:
+    enum BreakType {
+        None,
+        CharacterBoundary,       // not yet in use (UBRK_CHARACTER)
+        WordBoundary,            // calculated for all clusters (UBRK_WORD)
+        WordBreakWithoutHyphen,  // calculated only for hyphenated words
+        WordBreakWithHyphen,
+        SoftLineBreak,  // calculated for all clusters (UBRK_LINE)
+        HardLineBreak,  // calculated for all clusters (UBRK_LINE)
+    };
+
+    Cluster()
+            : fText(nullptr, 0)
+            , fRun(nullptr)
+            , fStart(0)
+            , fEnd()
+            , fWidth()
+            , fSpacing(0)
+            , fHeight()
+            , fWhiteSpaces(false)
+            , fBreakType(None) {}
+
+    Cluster(Run* run,
+              size_t start,
+              size_t end,
+              SkSpan<const char>
+                      text,
+              SkScalar width,
+              SkScalar height)
+            : fText(text)
+            , fRun(run)
+            , fStart(start)
+            , fEnd(end)
+            , fWidth(width)
+            , fSpacing(0)
+            , fHeight(height)
+            , fWhiteSpaces(false)
+            , fBreakType(None) {}
+
+    ~Cluster() = default;
+
+    SkScalar sizeToChar(const char* ch) const;
+    SkScalar sizeFromChar(const char* ch) const;
+
+    size_t roundPos(SkScalar s) const;
+
+    void space(SkScalar shift, SkScalar space) {
+        fSpacing += space;
+        fWidth += shift;
+    }
+
+    void setBreakType(BreakType type) { fBreakType = type; }
+    void setIsWhiteSpaces(bool ws) { fWhiteSpaces = ws; }
+    bool isWhitespaces() const { return fWhiteSpaces; }
+    bool canBreakLineAfter() const {
+        return fBreakType == SoftLineBreak || fBreakType == HardLineBreak;
+    }
+    bool isHardBreak() const { return fBreakType == HardLineBreak; }
+    bool isSoftBreak() const { return fBreakType == SoftLineBreak; }
+    Run* run() const { return fRun; }
+    size_t startPos() const { return fStart; }
+    size_t endPos() const { return fEnd; }
+    SkScalar width() const { return fWidth; }
+    SkScalar trimmedWidth() const { return fWidth - fSpacing; }
+    SkScalar lastSpacing() const { return fSpacing; }
+    SkScalar height() const { return fHeight; }
+    SkSpan<const char> text() const { return fText; }
+    size_t size() const { return fEnd - fStart; }
+
+    SkScalar trimmedWidth(size_t pos) const;
+
+    void shift(SkScalar offset) const { this->run()->shift(this, offset); }
+
+    void setIsWhiteSpaces();
+
+    bool contains(const char* ch) const { return ch >= fText.begin() && ch < fText.end(); }
+
+    bool belongs(SkSpan<const char> text) const {
+        return fText.begin() >= text.begin() && fText.end() <= text.end();
+    }
+
+    bool startsIn(SkSpan<const char> text) const {
+        return fText.begin() >= text.begin() && fText.begin() < text.end();
+    }
+
+private:
+    SkSpan<const char> fText;
+
+    Run* fRun;
+    size_t fStart;
+    size_t fEnd;
+    SkScalar fWidth;
+    SkScalar fSpacing;
+    SkScalar fHeight;
+    bool fWhiteSpaces;
+    BreakType fBreakType;
+};
+
+class LineMetrics {
+public:
+    LineMetrics() { clean(); }
+
+    LineMetrics(SkScalar a, SkScalar d, SkScalar l) {
+        fAscent = a;
+        fDescent = d;
+        fLeading = l;
+    }
+
+    LineMetrics(const SkFontMetrics& fm) {
+        fAscent = fm.fAscent;
+        fDescent = fm.fDescent;
+        fLeading = fm.fLeading;
+    }
+
+    void add(Run* run) {
+        fAscent = SkTMin(fAscent, run->ascent() * run->lineHeight());
+        fDescent = SkTMax(fDescent, run->descent() * run->lineHeight());
+        fLeading = SkTMax(fLeading, run->leading() * run->lineHeight());
+    }
+
+    void add(LineMetrics other) {
+        fAscent = SkTMin(fAscent, other.fAscent);
+        fDescent = SkTMax(fDescent, other.fDescent);
+        fLeading = SkTMax(fLeading, other.fLeading);
+    }
+    void clean() {
+        fAscent = 0;
+        fDescent = 0;
+        fLeading = 0;
+    }
+
+    SkScalar delta() const { return height() - ideographicBaseline(); }
+
+    void updateLineMetrics(LineMetrics& metrics, bool forceHeight) {
+        if (forceHeight) {
+            metrics.fAscent = fAscent;
+            metrics.fDescent = fDescent;
+            metrics.fLeading = fLeading;
+        } else {
+            metrics.fAscent = SkTMin(metrics.fAscent, fAscent);
+            metrics.fDescent = SkTMax(metrics.fDescent, fDescent);
+            metrics.fLeading = SkTMax(metrics.fLeading, fLeading);
+        }
+    }
+
+    SkScalar runTop(Run* run) const { return fLeading / 2 - fAscent + run->ascent() + delta(); }
+    SkScalar height() const { return SkScalarRoundToInt(fDescent - fAscent + fLeading); }
+    SkScalar alphabeticBaseline() const { return fLeading / 2 - fAscent; }
+    SkScalar ideographicBaseline() const { return fDescent - fAscent + fLeading; }
+    SkScalar baseline() const { return fLeading / 2 - fAscent; }
+    SkScalar ascent() const { return fAscent; }
+    SkScalar descent() const { return fDescent; }
+    SkScalar leading() const { return fLeading; }
+
+private:
+    SkScalar fAscent;
+    SkScalar fDescent;
+    SkScalar fLeading;
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // Run_DEFINED
diff --git a/modules/skparagraph/src/TextLine.cpp b/modules/skparagraph/src/TextLine.cpp
new file mode 100644
index 0000000..c389fcb
--- /dev/null
+++ b/modules/skparagraph/src/TextLine.cpp
@@ -0,0 +1,683 @@
+// Copyright 2019 Google LLC.
+#include "modules/skparagraph/src/TextLine.h"
+#include <unicode/brkiter.h>
+#include <unicode/ubidi.h>
+#include "modules/skparagraph/src/ParagraphImpl.h"
+
+#include "include/core/SkMaskFilter.h"
+#include "include/effects/SkDashPathEffect.h"
+#include "include/effects/SkDiscretePathEffect.h"
+#include "src/core/SkMakeUnique.h"
+
+namespace {
+
+SkSpan<const char> intersected(const SkSpan<const char>& a, const SkSpan<const char>& b) {
+    auto begin = SkTMax(a.begin(), b.begin());
+    auto end = SkTMin(a.end(), b.end());
+    return SkSpan<const char>(begin, end > begin ? end - begin : 0);
+}
+
+int32_t intersectedSize(SkSpan<const char> a, SkSpan<const char> b) {
+    if (a.begin() == nullptr || b.begin() == nullptr) {
+        return -1;
+    }
+    auto begin = SkTMax(a.begin(), b.begin());
+    auto end = SkTMin(a.end(), b.end());
+    return SkToS32(end - begin);
+}
+}  // namespace
+
+namespace skia {
+namespace textlayout {
+
+SkTHashMap<SkFont, Run> TextLine::fEllipsisCache;
+
+TextLine::TextLine(SkVector offset, SkVector advance, SkSpan<const TextBlock> blocks,
+                   SkSpan<const char> text, SkSpan<const char> textWithSpaces,
+                   SkSpan<const Cluster> clusters, size_t startPos, size_t endPos,
+                   LineMetrics sizes)
+        : fBlocks(blocks)
+        , fText(text)
+        , fTextWithSpaces(textWithSpaces)
+        , fClusters(clusters)
+        //, fStartPos(startPos)
+        //, fEndPos(endPos)
+        , fLogical()
+        , fShift(0)
+        , fAdvance(advance)
+        , fOffset(offset)
+        , fEllipsis(nullptr)
+        , fSizes(sizes) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    // Reorder visual runs
+    auto start = fClusters.begin();
+    auto end = fClusters.end() - 1;
+    size_t numRuns = end->run()->index() - start->run()->index() + 1;
+
+    // Get the logical order
+    std::vector<UBiDiLevel> runLevels;
+    for (auto run = start->run(); run <= end->run(); ++run) {
+        runLevels.emplace_back(run->fBidiLevel);
+    }
+
+    std::vector<int32_t> logicalOrder(numRuns);
+    ubidi_reorderVisual(runLevels.data(), SkToU32(numRuns), logicalOrder.data());
+
+    auto firstRun = start->run();
+    for (auto index : logicalOrder) {
+        fLogical.push_back(firstRun + index);
+    }
+
+    // TODO: use fStartPos and fEndPos really
+    // SkASSERT(fStartPos <= start->run()->size());
+    // SkASSERT(fEndPos <= end->run()->size());
+}
+
+TextLine::TextLine(TextLine&& other) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    this->fBlocks = other.fBlocks;
+    this->fText = other.fText;
+    this->fTextWithSpaces = other.fTextWithSpaces;
+    this->fLogical.reset();
+    this->fLogical = std::move(other.fLogical);
+    this->fShift = other.fShift;
+    this->fAdvance = other.fAdvance;
+    this->fOffset = other.fOffset;
+    this->fEllipsis = std::move(other.fEllipsis);
+    this->fSizes = other.sizes();
+    this->fClusters = other.fClusters;
+}
+
+void TextLine::paint(SkCanvas* textCanvas) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    if (this->empty()) {
+        return;
+    }
+
+    textCanvas->save();
+    textCanvas->translate(this->offset().fX, this->offset().fY);
+
+    this->iterateThroughStylesInTextOrder(
+            StyleType::kBackground,
+            [textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) {
+                return this->paintBackground(textCanvas, text, style, offsetX);
+            });
+
+    this->iterateThroughStylesInTextOrder(
+            StyleType::kShadow,
+            [textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) {
+                return this->paintShadow(textCanvas, text, style, offsetX);
+            });
+
+    this->iterateThroughStylesInTextOrder(
+            StyleType::kForeground,
+            [textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) {
+                return this->paintText(textCanvas, text, style, offsetX);
+            });
+
+    this->iterateThroughStylesInTextOrder(
+            StyleType::kDecorations,
+            [textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) {
+                return this->paintDecorations(textCanvas, text, style, offsetX);
+            });
+
+    textCanvas->restore();
+}
+
+void TextLine::format(TextAlign effectiveAlign, SkScalar maxWidth, bool notLastLine) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    SkScalar delta = maxWidth - this->width();
+    if (delta <= 0) {
+        return;
+    }
+
+    if (effectiveAlign == TextAlign::kJustify && notLastLine) {
+        this->justify(maxWidth);
+    } else if (effectiveAlign == TextAlign::kRight) {
+        this->shiftTo(delta);
+    } else if (effectiveAlign == TextAlign::kCenter) {
+        this->shiftTo(delta / 2);
+    }
+}
+
+void TextLine::scanStyles(StyleType style, const StyleVisitor& visitor) {
+    if (this->empty()) {
+        return;
+    }
+
+    this->iterateThroughStylesInTextOrder(
+            style, [this, visitor](SkSpan<const char> text, TextStyle style, SkScalar offsetX) {
+                visitor(text, style, offsetX);
+                return this->iterateThroughRuns(
+                        text, offsetX, false,
+                        [](Run*, int32_t, size_t, SkRect, SkScalar, bool) { return true; });
+            });
+}
+
+void TextLine::scanRuns(const RunVisitor& visitor) {
+    this->iterateThroughRuns(
+            fText, 0, false,
+            [visitor](Run* run, int32_t pos, size_t size, SkRect clip, SkScalar sc, bool b) {
+                visitor(run, pos, size, clip, sc, b);
+                return true;
+            });
+}
+
+SkScalar TextLine::paintText(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
+                             SkScalar offsetX) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    SkPaint paint;
+    if (style.hasForeground()) {
+        paint = style.getForeground();
+    } else {
+        paint.setColor(style.getColor());
+    }
+
+    auto shiftDown = this->baseline();
+    return this->iterateThroughRuns(
+            text, offsetX, false,
+            [paint, canvas, shiftDown](Run* run, int32_t pos, size_t size, SkRect clip,
+                                       SkScalar shift, bool clippingNeeded) {
+                SkTextBlobBuilder builder;
+                run->copyTo(builder, SkToU32(pos), size, SkVector::Make(0, shiftDown));
+                canvas->save();
+                if (clippingNeeded) {
+                    canvas->clipRect(clip);
+                }
+                canvas->translate(shift, 0);
+                canvas->drawTextBlob(builder.make(), 0, 0, paint);
+                canvas->restore();
+                return true;
+            });
+}
+
+SkScalar TextLine::paintBackground(SkCanvas* canvas, SkSpan<const char> text,
+                                   const TextStyle& style, SkScalar offsetX) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    return this->iterateThroughRuns(text, offsetX, false,
+                                    [canvas, style](Run* run, int32_t pos, size_t size, SkRect clip,
+                                                    SkScalar shift, bool clippingNeeded) {
+                                        if (style.hasBackground()) {
+                                            canvas->drawRect(clip, style.getBackground());
+                                        }
+                                        return true;
+                                    });
+}
+
+SkScalar TextLine::paintShadow(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
+                               SkScalar offsetX) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    if (style.getShadowNumber() == 0) {
+        // Still need to calculate text advance
+        return iterateThroughRuns(
+                text, offsetX, false,
+                [](Run*, int32_t, size_t, SkRect, SkScalar, bool) { return true; });
+    }
+
+    SkScalar result = 0;
+    for (TextShadow shadow : style.getShadows()) {
+        if (!shadow.hasShadow()) continue;
+
+        SkPaint paint;
+        paint.setColor(shadow.fColor);
+        if (shadow.fBlurRadius != 0.0) {
+            auto filter = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
+                                                 SkDoubleToScalar(shadow.fBlurRadius), false);
+            paint.setMaskFilter(filter);
+        }
+
+        auto shiftDown = this->baseline();
+        result = this->iterateThroughRuns(
+                text, offsetX, false,
+                [canvas, shadow, paint, shiftDown](Run* run, size_t pos, size_t size, SkRect clip,
+                                                   SkScalar shift, bool clippingNeeded) {
+                    SkTextBlobBuilder builder;
+                    run->copyTo(builder, pos, size, SkVector::Make(0, shiftDown));
+                    canvas->save();
+                    clip.offset(shadow.fOffset);
+                    if (clippingNeeded) {
+                        canvas->clipRect(clip);
+                    }
+                    canvas->translate(shift, 0);
+                    canvas->drawTextBlob(builder.make(), shadow.fOffset.x(), shadow.fOffset.y(),
+                                         paint);
+                    canvas->restore();
+                    return true;
+                });
+    }
+
+    return result;
+}
+
+SkScalar TextLine::paintDecorations(SkCanvas* canvas, SkSpan<const char> text,
+                                    const TextStyle& style, SkScalar offsetX) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    return this->iterateThroughRuns(
+            text, offsetX, false,
+            [this, canvas, style](Run* run, int32_t pos, size_t size, SkRect clip, SkScalar shift,
+                                  bool clippingNeeded) {
+                if (style.getDecoration() == TextDecoration::kNoDecoration) {
+                    return true;
+                }
+
+                for (auto decoration : AllTextDecorations) {
+                    if (style.getDecoration() && decoration == 0) {
+                        continue;
+                    }
+
+                    SkScalar thickness = style.getDecorationThicknessMultiplier();
+                    //
+                    SkScalar position = 0;
+                    switch (style.getDecoration()) {
+                        case TextDecoration::kUnderline:
+                            position = -run->ascent() + thickness;
+                            break;
+                        case TextDecoration::kOverline:
+                            position = 0;
+                            break;
+                        case TextDecoration::kLineThrough: {
+                            position = (run->descent() - run->ascent() - thickness) / 2;
+                            break;
+                        }
+                        default:
+                            // TODO: can we actually get here?
+                            break;
+                    }
+
+                    auto width = clip.width();
+                    SkScalar x = clip.left();
+                    SkScalar y = clip.top() + position;
+
+                    // Decoration paint (for now) and/or path
+                    SkPaint paint;
+                    SkPath path;
+                    this->computeDecorationPaint(paint, clip, style, path);
+                    paint.setStrokeWidth(thickness);
+
+                    switch (style.getDecorationStyle()) {
+                        case TextDecorationStyle::kWavy:
+                            path.offset(x, y);
+                            canvas->drawPath(path, paint);
+                            break;
+                        case TextDecorationStyle::kDouble: {
+                            canvas->drawLine(x, y, x + width, y, paint);
+                            SkScalar bottom = y + thickness * 2;
+                            canvas->drawLine(x, bottom, x + width, bottom, paint);
+                            break;
+                        }
+                        case TextDecorationStyle::kDashed:
+                        case TextDecorationStyle::kDotted:
+                        case TextDecorationStyle::kSolid:
+                            canvas->drawLine(x, y, x + width, y, paint);
+                            break;
+                        default:
+                            break;
+                    }
+                }
+
+                return true;
+            });
+}
+
+void TextLine::computeDecorationPaint(SkPaint& paint,
+                                      SkRect clip,
+                                      const TextStyle& style,
+                                      SkPath& path) const {
+    paint.setStyle(SkPaint::kStroke_Style);
+    if (style.getDecorationColor() == SK_ColorTRANSPARENT) {
+        paint.setColor(style.getColor());
+    } else {
+        paint.setColor(style.getDecorationColor());
+    }
+
+    SkScalar scaleFactor = style.getFontSize() / 14.f;
+
+    switch (style.getDecorationStyle()) {
+        case TextDecorationStyle::kSolid:
+            break;
+
+        case TextDecorationStyle::kDouble:
+            break;
+
+            // Note: the intervals are scaled by the thickness of the line, so it is
+            // possible to change spacing by changing the decoration_thickness
+            // property of TextStyle.
+        case TextDecorationStyle::kDotted: {
+            const SkScalar intervals[] = {1.0f * scaleFactor, 1.5f * scaleFactor,
+                                          1.0f * scaleFactor, 1.5f * scaleFactor};
+            size_t count = sizeof(intervals) / sizeof(intervals[0]);
+            paint.setPathEffect(SkPathEffect::MakeCompose(
+                    SkDashPathEffect::Make(intervals, (int32_t)count, 0.0f),
+                    SkDiscretePathEffect::Make(0, 0)));
+            break;
+        }
+            // Note: the intervals are scaled by the thickness of the line, so it is
+            // possible to change spacing by changing the decoration_thickness
+            // property of TextStyle.
+        case TextDecorationStyle::kDashed: {
+            const SkScalar intervals[] = {4.0f * scaleFactor, 2.0f * scaleFactor,
+                                          4.0f * scaleFactor, 2.0f * scaleFactor};
+            size_t count = sizeof(intervals) / sizeof(intervals[0]);
+            paint.setPathEffect(SkPathEffect::MakeCompose(
+                    SkDashPathEffect::Make(intervals, (int32_t)count, 0.0f),
+                    SkDiscretePathEffect::Make(0, 0)));
+            break;
+        }
+        case TextDecorationStyle::kWavy: {
+            int wave_count = 0;
+            SkScalar x_start = 0;
+            SkScalar wavelength = scaleFactor * style.getDecorationThicknessMultiplier();
+            auto width = clip.width();
+            path.moveTo(0, 0);
+            while (x_start + wavelength * 2 < width) {
+                path.rQuadTo(wavelength,
+                             wave_count % 2 != 0 ? wavelength : -wavelength,
+                             wavelength * 2,
+                             0);
+                x_start += wavelength * 2;
+                ++wave_count;
+            }
+            break;
+        }
+    }
+}
+
+void TextLine::justify(SkScalar maxWidth) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    // Count words and the extra spaces to spread across the line
+    // TODO: do it at the line breaking?..
+    size_t whitespacePatches = 0;
+    SkScalar textLen = 0;
+    bool whitespacePatch = false;
+    this->iterateThroughClustersInGlyphsOrder(
+            false, [&whitespacePatches, &textLen, &whitespacePatch](const Cluster* cluster) {
+                if (cluster->isWhitespaces()) {
+                    if (!whitespacePatch) {
+                        whitespacePatch = true;
+                        ++whitespacePatches;
+                    }
+                } else {
+                    whitespacePatch = false;
+                }
+                textLen += cluster->width();
+                return true;
+            });
+
+    if (whitespacePatches == 0) {
+        this->fShift = 0;
+        return;
+    }
+
+    SkScalar step = (maxWidth - textLen) / whitespacePatches;
+    SkScalar shift = 0;
+
+    // Spread the extra whitespaces
+    whitespacePatch = false;
+    this->iterateThroughClustersInGlyphsOrder(false, [&](const Cluster* cluster) {
+        if (cluster->isWhitespaces()) {
+            if (!whitespacePatch) {
+                shift += step;
+                whitespacePatch = true;
+                --whitespacePatches;
+            }
+        } else {
+            whitespacePatch = false;
+        }
+        cluster->shift(shift);
+        return true;
+    });
+
+    SkAssertResult(SkScalarNearlyEqual(shift, maxWidth - textLen));
+    SkASSERT(whitespacePatches == 0);
+    this->fShift = 0;
+    this->fAdvance.fX = maxWidth;
+}
+
+void TextLine::createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    // Replace some clusters with the ellipsis
+    // Go through the clusters in the reverse logical order
+    // taking off cluster by cluster until the ellipsis fits
+    SkScalar width = fAdvance.fX;
+    iterateThroughClustersInGlyphsOrder(
+            true, [this, &width, ellipsis, maxWidth](const Cluster* cluster) {
+                if (cluster->isWhitespaces()) {
+                    width -= cluster->width();
+                    return true;
+                }
+
+                // Shape the ellipsis
+                Run* cached = fEllipsisCache.find(cluster->run()->font());
+                if (cached == nullptr) {
+                    cached = shapeEllipsis(ellipsis, cluster->run());
+                }
+                fEllipsis = skstd::make_unique<Run>(*cached);
+
+                // See if it fits
+                if (width + fEllipsis->advance().fX > maxWidth) {
+                    width -= cluster->width();
+                    // Continue if it's not
+                    return true;
+                }
+
+                fEllipsis->shift(width, 0);
+                fAdvance.fX = width;
+                return false;
+            });
+}
+
+Run* TextLine::shapeEllipsis(const SkString& ellipsis, Run* run) {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    class ShapeHandler final : public SkShaper::RunHandler {
+    public:
+        explicit ShapeHandler(SkScalar lineHeight) : fRun(nullptr), fLineHeight(lineHeight) {}
+        Run* run() { return fRun; }
+
+    private:
+        void beginLine() override {}
+
+        void runInfo(const RunInfo&) override {}
+
+        void commitRunInfo() override {}
+
+        Buffer runBuffer(const RunInfo& info) override {
+            fRun = fEllipsisCache.set(info.fFont,
+                                      Run(SkSpan<const char>(), info, fLineHeight, 0, 0));
+            return fRun->newRunBuffer();
+        }
+
+        void commitRunBuffer(const RunInfo& info) override {
+            fRun->fAdvance.fX = info.fAdvance.fX;
+            fRun->fAdvance.fY = fRun->descent() - fRun->ascent();
+        }
+
+        void commitLine() override {}
+
+        Run* fRun;
+        SkScalar fLineHeight;
+    };
+
+    ShapeHandler handler(run->lineHeight());
+    std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
+    SkASSERT_RELEASE(shaper != nullptr);
+    shaper->shape(ellipsis.c_str(), ellipsis.size(), run->font(), true,
+                  std::numeric_limits<SkScalar>::max(), &handler);
+    handler.run()->fText = SkSpan<const char>(ellipsis.c_str(), ellipsis.size());
+    return handler.run();
+}
+
+SkRect TextLine::measureTextInsideOneRun(
+        SkSpan<const char> text, Run* run, size_t& pos, size_t& size, bool& clippingNeeded) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    SkASSERT(intersectedSize(run->text(), text) >= 0);
+
+    // Find [start:end] clusters for the text
+    bool found;
+    Cluster* start;
+    Cluster* end;
+    std::tie(found, start, end) = run->findLimitingClusters(text);
+    if (!found) {
+        SkASSERT(text.empty());
+        return SkRect::MakeEmpty();
+    }
+
+    pos = start->startPos();
+    size = end->endPos() - start->startPos();
+
+    // Calculate the clipping rectangle for the text with cluster edges
+    // There are 2 cases:
+    // EOL (when we expect the last cluster clipped without any spaces)
+    // Anything else (when we want the cluster width contain all the spaces -
+    // coming from letter spacing or word spacing or justification)
+    bool needsClipping = (run->leftToRight() ? end : start) == clusters().end() - 1;
+    SkRect clip =
+            SkRect::MakeXYWH(run->positionX(start->startPos()) - run->positionX(0),
+                             sizes().runTop(run),
+                             run->calculateWidth(start->startPos(), end->endPos(), needsClipping),
+                             run->calculateHeight());
+
+    // Correct the width in case the text edges don't match clusters
+    // TODO: This is where we get smart about selecting a part of a cluster
+    //  by shaping each grapheme separately and then use the result sizes
+    //  to calculate the proportions
+    auto leftCorrection = start->sizeToChar(text.begin());
+    auto rightCorrection = end->sizeFromChar(text.end() - 1);
+    clip.fLeft += leftCorrection;
+    clip.fRight -= rightCorrection;
+    clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
+
+    // SkDebugf("measureTextInsideOneRun: '%s'[%d:%d]\n", text.begin(), pos, pos + size);
+
+    return clip;
+}
+
+void TextLine::iterateThroughClustersInGlyphsOrder(bool reverse,
+                                                   const ClustersVisitor& visitor) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    for (size_t r = 0; r != fLogical.size(); ++r) {
+        auto& run = fLogical[reverse ? fLogical.size() - r - 1 : r];
+        // Walk through the clusters in the logical order (or reverse)
+        auto normalOrder = run->leftToRight() != reverse;
+        auto start = normalOrder ? run->clusters().begin() : run->clusters().end() - 1;
+        auto end = normalOrder ? run->clusters().end() : run->clusters().begin() - 1;
+        for (auto cluster = start; cluster != end; normalOrder ? ++cluster : --cluster) {
+            if (!this->contains(cluster)) {
+                continue;
+            }
+            if (!visitor(cluster)) {
+                return;
+            }
+        }
+    }
+}
+
+// Walk through the runs in the logical order
+SkScalar TextLine::iterateThroughRuns(SkSpan<const char> text,
+                                      SkScalar runOffset,
+                                      bool includeEmptyText,
+                                      const RunVisitor& visitor) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+
+    SkScalar width = 0;
+    for (auto& run : fLogical) {
+        // Only skip the text if it does not even touch the run
+        if (intersectedSize(run->text(), text) < 0) {
+            continue;
+        }
+
+        SkSpan<const char> intersect = intersected(run->text(), text);
+        if (run->text().empty() || intersect.empty()) {
+            continue;
+        }
+
+        size_t pos;
+        size_t size;
+        bool clippingNeeded;
+        SkRect clip = this->measureTextInsideOneRun(intersect, run, pos, size, clippingNeeded);
+        if (clip.height() == 0) {
+            continue;
+        }
+
+        auto shift = runOffset - clip.fLeft;
+        clip.offset(shift, 0);
+        if (clip.fRight > fAdvance.fX) {
+            clip.fRight = fAdvance.fX;
+            clippingNeeded = true;  // Correct the clip in case there was an ellipsis
+        } else if (run == fLogical.back() && this->ellipsis() != nullptr) {
+            clippingNeeded = true;  // To avoid trouble
+        }
+
+        if (!visitor(run, pos, size, clip, shift - run->positionX(0), clippingNeeded)) {
+            return width;
+        }
+
+        width += clip.width();
+        runOffset += clip.width();
+    }
+
+    if (this->ellipsis() != nullptr) {
+        auto ellipsis = this->ellipsis();
+        if (!visitor(ellipsis, 0, ellipsis->size(), ellipsis->clip(), ellipsis->clip().fLeft,
+                     false)) {
+            return width;
+        }
+        width += ellipsis->clip().width();
+    }
+
+    return width;
+}
+
+void TextLine::iterateThroughStylesInTextOrder(StyleType styleType,
+                                               const StyleVisitor& visitor) const {
+    TRACE_EVENT0("skia", TRACE_FUNC);
+    const char* start = nullptr;
+    size_t size = 0;
+    TextStyle prevStyle;
+
+    SkScalar offsetX = 0;
+    for (auto& block : fBlocks) {
+        auto intersect = intersected(block.text(), this->trimmedText());
+        if (intersect.empty()) {
+            if (start == nullptr) {
+                // This style is not applicable to the line
+                continue;
+            } else {
+                // We have found all the good styles already
+                break;
+            }
+        }
+
+        auto style = block.style();
+        if (start != nullptr && style.matchOneAttribute(styleType, prevStyle)) {
+            size += intersect.size();
+            continue;
+        } else if (size == 0) {
+            // First time only
+            prevStyle = style;
+            size = intersect.size();
+            start = intersect.begin();
+            continue;
+        }
+
+        auto width = visitor(SkSpan<const char>(start, size), prevStyle, offsetX);
+        offsetX += width;
+
+        // Start all over again
+        prevStyle = style;
+        start = intersect.begin();
+        size = intersect.size();
+    }
+
+    // The very last style
+    auto width = visitor(SkSpan<const char>(start, size), prevStyle, offsetX);
+    offsetX += width;
+
+    // This is a very important assert!
+    // It asserts that 2 different ways of calculation come with the same results
+    if (!SkScalarNearlyEqual(offsetX, this->width())) {
+        SkDebugf("ASSERT: %f != %f\n", offsetX, this->width());
+    }
+    SkASSERT(SkScalarNearlyEqual(offsetX, this->width()));
+}
+}  // namespace textlayout
+}  // namespace skia
diff --git a/modules/skparagraph/src/TextLine.h b/modules/skparagraph/src/TextLine.h
new file mode 100644
index 0000000..de71e38
--- /dev/null
+++ b/modules/skparagraph/src/TextLine.h
@@ -0,0 +1,132 @@
+// Copyright 2019 Google LLC.
+#ifndef TextLine_DEFINED
+#define TextLine_DEFINED
+
+#include "modules/skparagraph/src/Run.h"
+#include "include/core/SkCanvas.h"
+#include "include/private/SkTArray.h"
+#include "include/private/SkTHash.h"
+#include "modules/skparagraph/include/DartTypes.h"
+#include "modules/skparagraph/include/TextStyle.h"
+#include "src/core/SkSpan.h"
+
+namespace skia {
+namespace textlayout {
+
+class TextBlock {
+public:
+    TextBlock() : fText(), fTextStyle() {}
+    TextBlock(SkSpan<const char> text, const TextStyle& style) : fText(text), fTextStyle(style) {}
+
+    SkSpan<const char> text() const { return fText; }
+    TextStyle style() const { return fTextStyle; }
+
+    void add(SkSpan<const char> tail) {
+        SkASSERT(fText.end() == tail.begin());
+        fText = SkSpan<const char>(fText.begin(), fText.size() + tail.size());
+    }
+
+protected:
+    SkSpan<const char> fText;
+    TextStyle fTextStyle;
+};
+
+class TextLine {
+public:
+    TextLine() = default;
+    TextLine(TextLine&&);
+    ~TextLine() = default;
+
+    TextLine(SkVector offset, SkVector advance, SkSpan<const TextBlock> blocks,
+             SkSpan<const char> text, SkSpan<const char> textWithSpaces,
+             SkSpan<const Cluster> clusters, size_t start, size_t end, LineMetrics sizes);
+
+    SkSpan<const char> trimmedText() const { return fText; }
+     SkSpan<const char> textWithSpaces() const { return fTextWithSpaces; }
+    SkSpan<const Cluster> clusters() const { return fClusters; }
+    SkVector offset() const { return fOffset + SkVector::Make(fShift, 0); }
+    Run* ellipsis() const { return fEllipsis.get(); }
+    LineMetrics sizes() const { return fSizes; }
+    bool empty() const { return fText.empty(); }
+
+    SkScalar shift() const { return fShift; }
+    SkScalar height() const { return fAdvance.fY; }
+    SkScalar width() const {
+        return fAdvance.fX + (fEllipsis != nullptr ? fEllipsis->fAdvance.fX : 0);
+    }
+    void shiftTo(SkScalar shift) { fShift = shift; }
+
+    SkScalar alphabeticBaseline() const { return fSizes.alphabeticBaseline(); }
+    SkScalar ideographicBaseline() const { return fSizes.ideographicBaseline(); }
+    SkScalar baseline() const { return fSizes.baseline(); }
+    SkScalar roundingDelta() const { return fSizes.delta(); }
+
+    using StyleVisitor = std::function<SkScalar(SkSpan<const char> text, const TextStyle& style,
+                                                SkScalar offsetX)>;
+    void iterateThroughStylesInTextOrder(StyleType styleType, const StyleVisitor& visitor) const;
+
+    using RunVisitor = std::function<bool(Run* run, size_t pos, size_t size, SkRect clip,
+                                          SkScalar shift, bool clippingNeeded)>;
+    SkScalar iterateThroughRuns(SkSpan<const char> text,
+                                SkScalar offsetX,
+                                bool includeEmptyText,
+                                const RunVisitor& visitor) const;
+
+    using ClustersVisitor = std::function<bool(const Cluster* cluster)>;
+    void iterateThroughClustersInGlyphsOrder(bool reverse, const ClustersVisitor& visitor) const;
+
+    void format(TextAlign effectiveAlign, SkScalar maxWidth, bool last);
+    void paint(SkCanvas* canvas);
+
+    void createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool ltr);
+
+    // For testing internal structures
+    void scanStyles(StyleType style, const StyleVisitor& visitor);
+    void scanRuns(const RunVisitor& visitor);
+
+private:
+    Run* shapeEllipsis(const SkString& ellipsis, Run* run);
+    void justify(SkScalar maxWidth);
+
+    SkRect measureTextInsideOneRun(SkSpan<const char> text,
+                                   Run* run,
+                                   size_t& pos,
+                                   size_t& size,
+                                   bool& clippingNeeded) const;
+
+    SkScalar paintText(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
+                       SkScalar offsetX) const;
+    SkScalar paintBackground(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
+                             SkScalar offsetX) const;
+    SkScalar paintShadow(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
+                         SkScalar offsetX) const;
+    SkScalar paintDecorations(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style,
+                              SkScalar offsetX) const;
+
+    void computeDecorationPaint(SkPaint& paint, SkRect clip, const TextStyle& style,
+                                SkPath& path) const;
+
+    bool contains(const Cluster* cluster) const {
+        return cluster->text().begin() >= fText.begin() && cluster->text().end() <= fText.end();
+    }
+
+    SkSpan<const TextBlock> fBlocks;
+    SkSpan<const char> fText;
+    SkSpan<const char> fTextWithSpaces;
+    SkSpan<const Cluster> fClusters;
+    // TODO: To clip by glyph:
+    //size_t fStartPos;
+    //size_t fEndPos;
+    SkTArray<Run*, true> fLogical;
+    SkScalar fShift;                   // Shift to left - right - center
+    SkVector fAdvance;                 // Text size
+    SkVector fOffset;                  // Text position
+    std::unique_ptr<Run> fEllipsis;  // In case the line ends with the ellipsis
+    LineMetrics fSizes;              // Line metrics as a max of all run metrics
+
+    static SkTHashMap<SkFont, Run> fEllipsisCache;  // All found so far shapes of ellipsis
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // TextLine_DEFINED
diff --git a/modules/skparagraph/src/TextShadow.cpp b/modules/skparagraph/src/TextShadow.cpp
new file mode 100644
index 0000000..00ef8f2
--- /dev/null
+++ b/modules/skparagraph/src/TextShadow.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 Google LLC.
+#include "modules/skparagraph/include/TextShadow.h"
+#include "include/core/SkColor.h"
+
+namespace skia {
+namespace textlayout {
+
+TextShadow::TextShadow() = default;
+TextShadow::TextShadow(SkColor color, SkPoint offset, double blurRadius)
+        : fColor(color), fOffset(offset), fBlurRadius(blurRadius) {}
+
+bool TextShadow::operator==(const TextShadow& other) const {
+    if (fColor != other.fColor) return false;
+    if (fOffset != other.fOffset) return false;
+    if (fBlurRadius != other.fBlurRadius) return false;
+
+    return true;
+}
+
+bool TextShadow::operator!=(const TextShadow& other) const { return !(*this == other); }
+
+bool TextShadow::hasShadow() const {
+    if (!fOffset.isZero()) return true;
+    if (fBlurRadius != 0.0) return true;
+
+    return false;
+}
+
+}  // namespace textlayout
+}  // namespace skia
diff --git a/modules/skparagraph/src/TextStyle.cpp b/modules/skparagraph/src/TextStyle.cpp
new file mode 100644
index 0000000..8ba3d2d
--- /dev/null
+++ b/modules/skparagraph/src/TextStyle.cpp
@@ -0,0 +1,133 @@
+// Copyright 2019 Google LLC.
+#include "modules/skparagraph/include/TextStyle.h"
+#include "include/core/SkColor.h"
+#include "include/core/SkFontStyle.h"
+
+namespace skia {
+namespace textlayout {
+
+TextStyle::TextStyle() : fFontStyle() {
+    fFontFamilies.emplace_back(DEFAULT_FONT_FAMILY);
+    fColor = SK_ColorWHITE;
+    fDecoration = TextDecoration::kNoDecoration;
+    // Does not make sense to draw a transparent object, so we use it as a default
+    // value to indicate no decoration color was set.
+    fDecorationColor = SK_ColorTRANSPARENT;
+    fDecorationStyle = TextDecorationStyle::kSolid;
+    // Thickness is applied as a multiplier to the default thickness of the font.
+    fDecorationThicknessMultiplier = 1.0;
+    fFontSize = 14.0;
+    fLetterSpacing = 0.0;
+    fWordSpacing = 0.0;
+    fHeight = 1.0;
+    fHasBackground = false;
+    fHasForeground = false;
+    fTextBaseline = TextBaseline::kAlphabetic;
+}
+
+bool TextStyle::equals(const TextStyle& other) const {
+    if (fColor != other.fColor) {
+        return false;
+    }
+    if (fDecoration != other.fDecoration) {
+        return false;
+    }
+    if (fDecorationColor != other.fDecorationColor) {
+        return false;
+    }
+    if (fDecorationStyle != other.fDecorationStyle) {
+        return false;
+    }
+    if (fDecorationThicknessMultiplier != other.fDecorationThicknessMultiplier) {
+        return false;
+    }
+    if (!(fFontStyle == other.fFontStyle)) {
+        return false;
+    }
+    if (fFontFamilies != other.fFontFamilies) {
+        return false;
+    }
+    if (fLetterSpacing != other.fLetterSpacing) {
+        return false;
+    }
+    if (fWordSpacing != other.fWordSpacing) {
+        return false;
+    }
+    if (fHeight != other.fHeight) {
+        return false;
+    }
+    if (fFontSize != other.fFontSize) {
+        return false;
+    }
+    if (fLocale != other.fLocale) {
+        return false;
+    }
+    if (fHasForeground != other.fHasForeground || fForeground != other.fForeground) {
+        return false;
+    }
+    if (fHasBackground != other.fHasBackground || fBackground != other.fBackground) {
+        return false;
+    }
+    if (fTextShadows.size() != other.fTextShadows.size()) {
+        return false;
+    }
+
+    for (int32_t i = 0; i < (int32_t)fTextShadows.size(); ++i) {
+        if (fTextShadows[i] != other.fTextShadows[i]) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool TextStyle::matchOneAttribute(StyleType styleType, const TextStyle& other) const {
+    switch (styleType) {
+        case kForeground:
+            if (fHasForeground) {
+                return other.fHasForeground && fForeground == other.fForeground;
+            } else {
+                return !other.fHasForeground && fColor == other.fColor;
+            }
+
+        case kBackground:
+            return (fHasBackground == other.fHasBackground && fBackground == other.fBackground);
+
+        case kShadow:
+            if (fTextShadows.size() != other.fTextShadows.size()) {
+                return false;
+            }
+
+            for (int32_t i = 0; i < SkToInt(fTextShadows.size()); ++i) {
+                if (fTextShadows[i] != other.fTextShadows[i]) {
+                    return false;
+                }
+            }
+            return true;
+
+        case kDecorations:
+            return fDecoration == other.fDecoration && fDecorationColor == other.fDecorationColor &&
+                   fDecorationStyle == other.fDecorationStyle &&
+                   fDecorationThicknessMultiplier == other.fDecorationThicknessMultiplier;
+
+        case kLetterSpacing:
+            return fLetterSpacing == other.fLetterSpacing;
+
+        case kWordSpacing:
+            return fWordSpacing == other.fWordSpacing;
+
+        case kAllAttributes:
+            return this->equals(other);
+
+        case kFont:
+            return fFontStyle == other.fFontStyle && fFontFamilies == other.fFontFamilies &&
+                   fFontSize == other.fFontSize && fHeight == other.fHeight;
+
+        default:
+            SkASSERT(false);
+            return false;
+    }
+}
+
+}  // namespace textlayout
+}  // namespace skia
diff --git a/modules/skparagraph/src/TextWrapper.cpp b/modules/skparagraph/src/TextWrapper.cpp
new file mode 100644
index 0000000..f8adfd1
--- /dev/null
+++ b/modules/skparagraph/src/TextWrapper.cpp
@@ -0,0 +1,213 @@
+// Copyright 2019 Google LLC.
+#include "modules/skparagraph/src/TextWrapper.h"
+#include "modules/skparagraph/src/ParagraphImpl.h"
+
+namespace skia {
+namespace textlayout {
+
+// Since we allow cluster clipping when they don't fit
+// we have to work with stretches - parts of clusters
+void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) {
+    fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
+    fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
+    fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
+    for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
+        if (fWords.width() + fClusters.width() + cluster->width() > maxWidth) {
+            if (cluster->isWhitespaces()) {
+                break;
+            }
+            if (cluster->width() > maxWidth) {
+                // Break the cluster into parts by glyph position
+                auto delta = maxWidth - (fWords.width() + fClusters.width());
+                fClip.extend(cluster, cluster->roundPos(delta));
+                fTooLongCluster = true;
+                fTooLongWord = true;
+                break;
+            }
+
+            // Walk further to see if there is a too long word, cluster or glyph
+            SkScalar nextWordLength = fClusters.width();
+            for (auto further = cluster; further != endOfClusters; ++further) {
+                if (further->isSoftBreak() || further->isHardBreak()) {
+                    break;
+                }
+                nextWordLength += further->width();
+            }
+            if (nextWordLength > maxWidth) {
+                // If the word is too long we can break it right now and hope it's enough
+                fTooLongWord = true;
+            }
+
+            // TODO: this is the place when we use hyphenation
+            fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, nextWordLength);
+            break;
+        }
+
+        fClusters.extend(cluster);
+
+        // Keep adding clusters/words
+        if (fClusters.endOfWord()) {
+            fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, getClustersTrimmedWidth());
+            fWords.extend(fClusters);
+        }
+
+        if ((fHardLineBreak = cluster->isHardBreak())) {
+            // Stop at the hard line break
+            break;
+        }
+    }
+}
+
+void TextWrapper::moveForward() {
+    do {
+        if (fWords.width() > 0) {
+            fEndLine.extend(fWords);
+        } else if (fClusters.width() > 0) {
+            fEndLine.extend(fClusters);
+            fTooLongWord = false;
+        } else if (fClip.width() > 0) {
+            fEndLine.extend(fClip);
+            fTooLongWord = false;
+            fTooLongCluster = false;
+        } else {
+            break;
+        }
+    } while (fTooLongWord || fTooLongCluster);
+}
+
+// Special case for start/end cluster since they can be clipped
+void TextWrapper::trimEndSpaces(bool includingClusters) {
+    // Remember the breaking position
+    fEndLine.saveBreak();
+    if (includingClusters) {
+        // Move the end of the line to the left
+        for (auto cluster = fEndLine.endCluster();
+             cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
+             --cluster) {
+            fEndLine.trim(cluster);
+        }
+    }
+    fEndLine.trim();
+}
+
+SkScalar TextWrapper::getClustersTrimmedWidth() {
+    // Move the end of the line to the left
+    SkScalar width = fClusters.width();
+    auto cluster = fClusters.endCluster();
+    for (; cluster > fClusters.startCluster() && cluster->isWhitespaces(); --cluster) {
+        width -= cluster->width();
+    }
+    if (cluster >= fClusters.startCluster()) {
+        width -= (cluster->width() - cluster->trimmedWidth(cluster->endPos()));
+    }
+    return width;
+}
+
+// Trim the beginning spaces in case of soft line break
+void TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
+    // Restore the breaking position
+    fEndLine.restoreBreak();
+    fEndLine.nextPos();
+    if (fHardLineBreak) {
+        // End of line is always end of cluster, but need to skip \n
+        fEndLine.startFrom(fEndLine.endCluster(), 0);
+        return;
+    }
+    if (fEndLine.endPos() != 0) {
+        // Clipping
+        fEndLine.startFrom(fEndLine.endCluster(), fEndLine.endPos());
+        return;
+    }
+
+    auto cluster = fEndLine.endCluster();
+    while (cluster < endOfClusters && cluster->isWhitespaces()) {
+        ++cluster;
+    }
+    fEndLine.startFrom(cluster, 0);
+}
+
+void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
+                                     SkScalar maxWidth,
+                                     const AddLineToParagraph& addLine) {
+    auto span = parent->clusters();
+    auto maxLines = parent->paragraphStyle().getMaxLines();
+    auto ellipsisStr = parent->paragraphStyle().getEllipsis();
+    auto textAlign = parent->paragraphStyle().effective_align();
+
+    fHeight = 0;
+    fMinIntrinsicWidth = 0;
+    fMaxIntrinsicWidth = 0;
+    fEndLine = TextStretch(span.begin(), span.begin());
+    auto end = &span.back();
+    while (fEndLine.endCluster() != end) {
+        reset();
+
+        lookAhead(maxWidth, end);
+        moveForward();
+
+        // Do not trim end spaces on the naturally last line of the left aligned text
+        trimEndSpaces(textAlign != TextAlign::kLeft || fEndLine.endCluster() < end - 1);
+
+        auto lastLine = maxLines == std::numeric_limits<size_t>::max() ||
+            fLineNumber >= maxLines;
+        auto needEllipsis =
+            lastLine &&
+                !fHardLineBreak &&
+                fEndLine.endCluster() < end - 1 &&
+                maxWidth != std::numeric_limits<SkScalar>::max() &&
+                !ellipsisStr.isEmpty();
+        // TODO: perform ellipsis work here
+        if (parent->strutEnabled()) {
+            // Make sure font metrics are not less than the strut
+            parent->strutMetrics().updateLineMetrics(fEndLine.metrics(),
+                                                     parent->strutForceHeight());
+        }
+        fMaxIntrinsicWidth = SkMaxScalar(fMaxIntrinsicWidth, fEndLine.width());
+        // TODO: keep start/end/break info for text and runs but in a better way that below
+        SkSpan<const char> text(fEndLine.startCluster()->text().begin(),
+                                fEndLine.endCluster()->text().end() - fEndLine.startCluster()->text().begin());
+        SkSpan<const char> textWithSpaces(fEndLine.startCluster()->text().begin(),
+                                fEndLine.breakCluster()->text().end() - fEndLine.startCluster()->text().begin());
+        addLine(text, textWithSpaces,
+                fEndLine.startCluster(),
+                fEndLine.endCluster(),
+                fEndLine.startPos(),
+                fEndLine.endPos(),
+                SkVector::Make(0, fHeight),
+                SkVector::Make(fEndLine.width(), fEndLine.metrics().height()),
+                fEndLine.metrics(),
+                needEllipsis);
+
+        // Start a new line
+        fHeight += fEndLine.metrics().height();
+
+        trimStartSpaces(end);
+
+        if (needEllipsis || fLineNumber >= maxLines) {
+            break;
+        }
+        ++fLineNumber;
+    }
+
+    if (fHardLineBreak) {
+        // Last character is a line break
+        if (parent->strutEnabled()) {
+            // Make sure font metrics are not less than the strut
+            parent->strutMetrics().updateLineMetrics(fEndLine.metrics(),
+                                                     parent->strutForceHeight());
+        }
+        SkSpan<const char> empty(fEndLine.breakCluster()->text().begin(), 0);
+        addLine(empty, empty,
+                fEndLine.breakCluster(),
+                fEndLine.breakCluster(),
+                0,
+                0,
+                SkVector::Make(0, fHeight),
+                SkVector::Make(0, fEndLine.metrics().height()),
+                fEndLine.metrics(),
+                false);
+    }
+}
+
+}  // namespace textlayout
+}  // namespace skia
diff --git a/modules/skparagraph/src/TextWrapper.h b/modules/skparagraph/src/TextWrapper.h
new file mode 100644
index 0000000..2ae80a5
--- /dev/null
+++ b/modules/skparagraph/src/TextWrapper.h
@@ -0,0 +1,183 @@
+// Copyright 2019 Google LLC.
+#ifndef TextWrapper_DEFINED
+#define TextWrapper_DEFINED
+
+#include <string>
+#include "modules/skparagraph/src/TextLine.h"
+#include "src/core/SkSpan.h"
+
+namespace skia {
+namespace textlayout {
+
+class ParagraphImpl;
+
+class TextWrapper {
+    class ClusterPos {
+    public:
+        ClusterPos() : fCluster(nullptr), fPos(0) {}
+        ClusterPos(Cluster* cluster, size_t pos) : fCluster(cluster), fPos(pos) {}
+        Cluster* cluster() const { return fCluster; }
+        size_t position() const { return fPos; }
+        void move(bool up) {
+            fCluster += up ? 1 : -1;
+            fPos = up ? 0 : fCluster->endPos();
+        }
+        void setPosition(size_t pos) { fPos = pos; }
+        void clean() {
+            fCluster = nullptr;
+            fPos = 0;
+        }
+
+    private:
+        Cluster* fCluster;
+        size_t fPos;
+    };
+    class TextStretch {
+    public:
+        TextStretch() : fStart(), fEnd(), fWidth(0) {}
+        explicit TextStretch(Cluster* s, Cluster* e)
+                : fStart(s, 0), fEnd(e, e->endPos()), fMetrics(), fWidth(0) {
+            for (auto c = s; c <= e; ++c) {
+                if (c->run() != nullptr) {
+                    fMetrics.add(c->run());
+                }
+            }
+        }
+
+        SkScalar width() const { return fWidth; }
+        Cluster* startCluster() const { return fStart.cluster(); }
+        Cluster* endCluster() const { return fEnd.cluster(); }
+        Cluster* breakCluster() const { return fBreak.cluster(); }
+        LineMetrics& metrics() { return fMetrics; }
+        size_t startPos() const { return fStart.position(); }
+        size_t endPos() const { return fEnd.position(); }
+        bool endOfCluster() { return fEnd.position() == fEnd.cluster()->endPos(); }
+        bool endOfWord() {
+            return endOfCluster() &&
+                   (fEnd.cluster()->isHardBreak() || fEnd.cluster()->isSoftBreak());
+        }
+
+        void extend(TextStretch& stretch) {
+            fMetrics.add(stretch.fMetrics);
+            fEnd = stretch.fEnd;
+            fWidth += stretch.fWidth;
+            stretch.clean();
+        }
+
+        void extend(Cluster* cluster) {
+            fEnd = ClusterPos(cluster, cluster->endPos());
+            fMetrics.add(cluster->run());
+            fWidth += cluster->width();
+        }
+
+        void extend(Cluster* cluster, size_t pos) {
+            fEnd = ClusterPos(cluster, pos);
+            if (cluster->run() != nullptr) {
+                fMetrics.add(cluster->run());
+            }
+        }
+
+        void startFrom(Cluster* cluster, size_t pos) {
+            fStart = ClusterPos(cluster, pos);
+            fEnd = ClusterPos(cluster, pos);
+            if (cluster->run() != nullptr) {
+                fMetrics.add(cluster->run());
+            }
+            fWidth = 0;
+        }
+
+        void nextPos() {
+            if (fEnd.position() == fEnd.cluster()->endPos()) {
+                fEnd.move(true);
+            } else {
+                fEnd.setPosition(fEnd.cluster()->endPos());
+            }
+        }
+
+        void saveBreak() { fBreak = fEnd; }
+
+        void restoreBreak() { fEnd = fBreak; }
+
+        void trim() {
+            fWidth -= (fEnd.cluster()->width() - fEnd.cluster()->trimmedWidth(fEnd.position()));
+        }
+
+        void trim(Cluster* cluster) {
+            SkASSERT(fEnd.cluster() == cluster);
+            if (fEnd.cluster() > fStart.cluster()) {
+                fEnd.move(false);
+                fWidth -= cluster->width();
+            } else {
+                fWidth = 0;
+            }
+        }
+
+        void clean() {
+            fStart.clean();
+            fEnd.clean();
+            fWidth = 0;
+            fMetrics.clean();
+        }
+
+    private:
+        ClusterPos fStart;
+        ClusterPos fEnd;
+        ClusterPos fBreak;
+        LineMetrics fMetrics;
+        SkScalar fWidth;
+    };
+
+public:
+    TextWrapper() { fLineNumber = 1; }
+
+    using AddLineToParagraph = std::function<void(SkSpan<const char> text,
+                                                  SkSpan<const char> textWithSpaces,
+                                                  Cluster* start,
+                                                  Cluster* end,
+                                                  size_t startClip,
+                                                  size_t endClip,
+                                                  SkVector offset,
+                                                  SkVector advance,
+                                                  LineMetrics metrics,
+                                                  bool addEllipsis)>;
+    void breakTextIntoLines(ParagraphImpl* parent,
+                            SkScalar maxWidth,
+                            const AddLineToParagraph& addLine);
+
+    SkScalar height() const { return fHeight; }
+    SkScalar minIntrinsicWidth() const { return fMinIntrinsicWidth; }
+    SkScalar maxIntrinsicWidth() const { return fMaxIntrinsicWidth; }
+
+private:
+    TextStretch fWords;
+    TextStretch fClusters;
+    TextStretch fClip;
+    TextStretch fEndLine;
+    size_t fLineNumber;
+    bool fTooLongWord;
+    bool fTooLongCluster;
+
+    bool fHardLineBreak;
+
+    SkScalar fHeight;
+    SkScalar fMinIntrinsicWidth;
+    SkScalar fMaxIntrinsicWidth;
+
+    void reset() {
+        fWords.clean();
+        fClusters.clean();
+        fClip.clean();
+        fTooLongCluster = false;
+        fTooLongWord = false;
+    }
+
+    void lookAhead(SkScalar maxWidth, Cluster* endOfClusters);
+    void moveForward();
+    void trimEndSpaces(bool includingClusters);
+    void trimStartSpaces(Cluster* endOfClusters);
+    SkScalar getClustersTrimmedWidth();
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // TextWrapper_DEFINED
diff --git a/modules/skparagraph/src/TypefaceFontProvider.cpp b/modules/skparagraph/src/TypefaceFontProvider.cpp
new file mode 100644
index 0000000..ed4d776
--- /dev/null
+++ b/modules/skparagraph/src/TypefaceFontProvider.cpp
@@ -0,0 +1,86 @@
+// Copyright 2019 Google LLC.
+#include "modules/skparagraph/src/TypefaceFontProvider.h"
+#include <algorithm>
+#include "include/core/SkString.h"
+#include "include/core/SkTypeface.h"
+
+namespace skia {
+namespace textlayout {
+
+int TypefaceFontProvider::onCountFamilies() const { return fRegisteredFamilies.size(); }
+
+void TypefaceFontProvider::onGetFamilyName(int index, SkString* familyName) const {
+    SkASSERT(index < fRegisteredFamilies.count());
+    familyName->set(fRegisteredFamilies[index]->getFamilyName());
+}
+
+SkFontStyleSet* TypefaceFontProvider::onMatchFamily(const char familyName[]) const {
+    for (auto& family : fRegisteredFamilies) {
+        if (family->getFamilyName().equals(familyName)) {
+            return SkRef(family.get());
+        }
+    }
+    return nullptr;
+}
+
+size_t TypefaceFontProvider::registerTypeface(sk_sp<SkTypeface> typeface) {
+    if (typeface == nullptr) {
+        return 0;
+    }
+
+    SkString familyName;
+    typeface->getFamilyName(&familyName);
+
+    return registerTypeface(std::move(typeface), familyName);
+}
+
+size_t TypefaceFontProvider::registerTypeface(sk_sp<SkTypeface> typeface, const SkString& familyName) {
+    if (familyName.size() == 0) {
+        return 0;
+    }
+
+    TypefaceFontStyleSet* found = nullptr;
+    for (auto& family : fRegisteredFamilies) {
+        if (family->getFamilyName().equals(familyName)) {
+            found = family.get();
+            break;
+        }
+    }
+    if (found == nullptr) {
+        found = fRegisteredFamilies.emplace_back(sk_make_sp<TypefaceFontStyleSet>(familyName)).get();
+    }
+
+    found->appendTypeface(std::move(typeface));
+    return 1;
+}
+
+TypefaceFontStyleSet::TypefaceFontStyleSet(const SkString& familyName)
+        : fFamilyName(familyName) {}
+
+int TypefaceFontStyleSet::count() { return fStyles.size(); }
+
+void TypefaceFontStyleSet::getStyle(int index, SkFontStyle* style, SkString* name) {
+    SkASSERT(index < fStyles.count());
+    if (style) {
+        *style = fStyles[index]->fontStyle();
+    }
+    if (name) {
+        *name = fFamilyName;
+    }
+}
+
+SkTypeface* TypefaceFontStyleSet::createTypeface(int index) {
+    SkASSERT(index < fStyles.count());
+    return SkRef(fStyles[index].get());
+}
+
+SkTypeface* TypefaceFontStyleSet::matchStyle(const SkFontStyle& pattern) {
+    return this->matchStyleCSS3(pattern);
+}
+
+void TypefaceFontStyleSet::appendTypeface(sk_sp<SkTypeface> typeface) {
+    fStyles.emplace_back(std::move(typeface));
+}
+
+}  // namespace textlayout
+}  // namespace skia
diff --git a/modules/skparagraph/src/TypefaceFontProvider.h b/modules/skparagraph/src/TypefaceFontProvider.h
new file mode 100644
index 0000000..e72a58d
--- /dev/null
+++ b/modules/skparagraph/src/TypefaceFontProvider.h
@@ -0,0 +1,89 @@
+// Copyright 2019 Google LLC.
+#ifndef TypefaceFontProvider_DEFINED
+#define TypefaceFontProvider_DEFINED
+
+#include <include/private/SkTArray.h>
+#include <include/private/SkTHash.h>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "include/core/SkFontMgr.h"
+#include "include/core/SkStream.h"
+#include "include/core/SkString.h"
+#include "src/core/SkFontDescriptor.h"
+
+namespace skia {
+namespace textlayout {
+
+class TypefaceFontStyleSet : public SkFontStyleSet {
+public:
+    explicit TypefaceFontStyleSet(const SkString& familyName);
+
+    int count() override;
+    void getStyle(int index, SkFontStyle*, SkString* name) override;
+    SkTypeface* createTypeface(int index) override;
+    SkTypeface* matchStyle(const SkFontStyle& pattern) override;
+
+    SkString getFamilyName() const { return fFamilyName; }
+    SkString getAlias() const { return fAlias; }
+    void appendTypeface(sk_sp<SkTypeface> typeface);
+
+private:
+    SkTArray<sk_sp<SkTypeface>> fStyles;
+    SkString fFamilyName;
+    SkString fAlias;
+};
+
+class TypefaceFontProvider : public SkFontMgr {
+public:
+    size_t registerTypeface(sk_sp<SkTypeface> typeface);
+    size_t registerTypeface(sk_sp<SkTypeface> typeface, const SkString& alias);
+
+    int onCountFamilies() const override;
+
+    void onGetFamilyName(int index, SkString* familyName) const override;
+
+    SkFontStyleSet* onMatchFamily(const char familyName[]) const override;
+
+    SkFontStyleSet* onCreateStyleSet(int index) const override { return nullptr; }
+    SkTypeface* onMatchFamilyStyle(const char familyName[],
+                                   const SkFontStyle& style) const override {
+        return nullptr;
+    }
+    SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle& style,
+                                            const char* bcp47[], int bcp47Count,
+                                            SkUnichar character) const override {
+        return nullptr;
+    }
+    SkTypeface* onMatchFaceStyle(const SkTypeface* tf, const SkFontStyle& style) const override {
+        return nullptr;
+    }
+
+    sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override { return nullptr; }
+    sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
+                                            int ttcIndex) const override {
+        return nullptr;
+    }
+    sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
+                                           const SkFontArguments&) const override {
+        return nullptr;
+    }
+    sk_sp<SkTypeface> onMakeFromFontData(std::unique_ptr<SkFontData>) const override {
+        return nullptr;
+    }
+    sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
+        return nullptr;
+    }
+
+    sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[],
+                                           SkFontStyle style) const override {
+        return nullptr;
+    }
+
+private:
+    SkTArray<sk_sp<TypefaceFontStyleSet>> fRegisteredFamilies;
+};
+}  // namespace textlayout
+}  // namespace skia
+
+#endif  // TypefaceFontProvider_DEFINED
diff --git a/modules/skshaper/BUILD.gn b/modules/skshaper/BUILD.gn
index a672d98..ee1f9ab 100644
--- a/modules/skshaper/BUILD.gn
+++ b/modules/skshaper/BUILD.gn
@@ -39,4 +39,4 @@
 } else {
   group("skshaper") {
   }
-}
+}
\ No newline at end of file