[fuzzing] Add first pass SkParagraph fuzzing.

Rough first pass at SkParagraph fuzzing. Lots of things not yet fuzzed.
--FontCollection cribbed from SkParagraphTest
--Current flow:
---Fuzz ParagraphStyle
---Add text and style some random small number of times.
---Text is either ASCII, unicode, or 'Zalgo'.

Although there are many todos, want to go ahead and submit this
~unchanged so the existing test cases that have found bugs are
not invalidated by a changing binary.

Change-Id: I38adca5fa79cfb20068fdf2fb431f90de55a2afc
Bug: skia:10894
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/336438
Commit-Queue: Weston Tracey <westont@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/fuzz/FuzzSkParagraph.cpp b/fuzz/FuzzSkParagraph.cpp
new file mode 100644
index 0000000..f3f396c
--- /dev/null
+++ b/fuzz/FuzzSkParagraph.cpp
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "fuzz/Fuzz.h"
+#include "fuzz/FuzzCommon.h"
+#include "include/core/SkBitmap.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkColor.h"
+#include "include/core/SkEncodedImageFormat.h"
+#include "include/core/SkFontMgr.h"
+#include "include/core/SkFontStyle.h"
+#include "include/core/SkImageEncoder.h"
+#include "include/core/SkPaint.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkRect.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkStream.h"
+#include "include/core/SkString.h"
+#include "include/core/SkTypeface.h"
+#include "include/core/SkTypes.h"
+#include "modules/skparagraph/include/DartTypes.h"
+#include "modules/skparagraph/include/FontCollection.h"
+#include "modules/skparagraph/include/Paragraph.h"
+#include "modules/skparagraph/include/ParagraphCache.h"
+#include "modules/skparagraph/include/ParagraphStyle.h"
+#include "modules/skparagraph/include/TextShadow.h"
+#include "modules/skparagraph/include/TextStyle.h"
+#include "modules/skparagraph/include/TypefaceFontProvider.h"
+#include "modules/skparagraph/src/ParagraphBuilderImpl.h"
+#include "modules/skparagraph/src/ParagraphImpl.h"
+#include "modules/skparagraph/src/Run.h"
+#include "modules/skparagraph/src/TextLine.h"
+#include "modules/skparagraph/utils/TestFontCollection.h"
+#include "src/core/SkOSFile.h"
+#include "src/core/SkSpan.h"
+#include "src/utils/SkOSPath.h"
+#include "src/utils/SkShaperJSONWriter.h"
+#include "tests/Test.h"
+#include "tools/Resources.h"
+
+
+#include <string.h>
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace skia::textlayout;
+namespace {
+const uint8_t MAX_TEXT_LENGTH = 255;
+const uint8_t MAX_TEXT_ADDITIONS = 4;
+const uint16_t TEST_CANVAS_WIDTH = 1000;
+
+class ResourceFontCollection : public FontCollection {
+public:
+    ResourceFontCollection(bool testOnly = false)
+            : fFontsFound(false)
+            , fResolvedFonts(0)
+            , fResourceDir(GetResourcePath("fonts").c_str())
+            , fFontProvider(sk_make_sp<TypefaceFontProvider>()) {
+        std::vector<SkString> fonts;
+        SkOSFile::Iter iter(fResourceDir.c_str());
+
+        SkString path;
+        while (iter.next(&path)) {
+            if (path.endsWith("Roboto-Italic.ttf")) {
+                fFontsFound = true;
+            }
+            fonts.emplace_back(path);
+        }
+
+        if (!fFontsFound) {
+            // SkDebugf("Fonts not found, skipping all the tests\n");
+            return;
+        }
+        // Only register fonts if we have to
+        for (auto& font : fonts) {
+            SkString file_path;
+            file_path.printf("%s/%s", fResourceDir.c_str(), font.c_str());
+            fFontProvider->registerTypeface(SkTypeface::MakeFromFile(file_path.c_str()));
+        }
+
+        if (testOnly) {
+            this->setTestFontManager(std::move(fFontProvider));
+        } else {
+            this->setAssetFontManager(std::move(fFontProvider));
+        }
+        this->disableFontFallback();
+    }
+
+    size_t resolvedFonts() const { return fResolvedFonts; }
+
+    // TODO: temp solution until we check in fonts
+    bool fontsFound() const { return fFontsFound; }
+
+private:
+    bool fFontsFound;
+    size_t fResolvedFonts;
+    std::string fResourceDir;
+    sk_sp<TypefaceFontProvider> fFontProvider;
+};
+
+// buffer must be at least MAX_TEXT_LENGTH in length.
+// Returns size of text placed in buffer.
+template <typename T>
+uint8_t RandomText(T* buffer, Fuzz* fuzz) {
+    uint8_t text_length;
+    fuzz->nextRange(&text_length, 0, MAX_TEXT_LENGTH);
+    fuzz->nextN(buffer, text_length);
+    return text_length;
+}
+
+// Add random bytes to the paragraph.
+void AddASCIIText(ParagraphBuilder* builder,Fuzz* fuzz) {
+    char text[MAX_TEXT_LENGTH];
+    const auto text_length = RandomText(text, fuzz);
+    builder->addText(text, text_length);
+}
+// Add random bytes to the paragraph.
+void AddUnicodeText(ParagraphBuilder* builder,Fuzz* fuzz) {
+    char16_t text[MAX_TEXT_LENGTH];
+    const auto text_length = RandomText(text, fuzz);
+    builder->addText(std::u16string(text, text_length));
+}
+
+// Combining characters to produce 'Zalgo' text.
+const std::u16string COMBINING_DOWN = u"\u0316\u0317\u0318\u0319\u031c\u031d\u031e\u031f\u0320\u0324\u0325\u0326\u0329\u032a\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0339\u033a\u033b\u033c\u0345\u0347\u0348\u0349\u034d\u034e\u0353\u0354\u0355\u0356\u0359\u035a\u0323";
+const std::u16string COMBINING_UP = u"\u030d\u030e\u0304\u0305\u033f\u0311\u0306\u0310\u0352\u0357\u0351\u0307\u0308\u030a\u0342\u0343\u0344\u034a\u034b\u034c\u0303\u0302\u030c\u0350\u0300\u0301\u030b\u030f\u0312\u0313\u0314\u033d\u0309\u0363\u0364\u0365\u0366\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u035b\u0346\u031a";
+const std::u16string COMBINING_MIDDLE = u"\u0315\u031b\u0340\u0341\u0358\u0321\u0322\u0327\u0328\u0334\u0335\u0336\u034f\u035c\u035d\u035e\u035f\u0360\u0362\u0338\u0337\u0361\u0489";
+// Add random Zalgo text to the paragraph.
+void AddZalgoText(ParagraphBuilder* builder, Fuzz* fuzz) {
+    char text[MAX_TEXT_LENGTH];
+    const auto text_length = RandomText(text, fuzz);
+    std::u16string result;
+
+    for (auto& c : std::string(text, text_length)) {
+        result += c;
+        uint8_t mark_count;
+        fuzz->next(&mark_count);
+        for (int i = 0; i < mark_count; i++) {
+            uint8_t mark_type, mark_index;
+            fuzz->next(&mark_type, &mark_index);
+            switch (mark_type % 3) {
+                case 0:
+                    result += COMBINING_UP[mark_index % COMBINING_UP.size()];
+                    break;
+                case 1:
+                    result += COMBINING_MIDDLE[mark_index % COMBINING_MIDDLE.size()];
+                    break;
+                case 2:
+                default:
+                    result += COMBINING_DOWN[mark_index % COMBINING_DOWN.size()];
+                    break;
+            }
+        }
+    }
+    builder->addText(result);
+}
+
+void AddStyle(ParagraphBuilder* builder, Fuzz*  fuzz) {
+    // TODO(westont): Make this probabilistic, and fill in the remaining TextStyle fields.
+    TextStyle ts;
+    ts.setFontFamilies({SkString("Roboto")});
+    //ts.setColor(SK_ColorBLACK);
+    //ts.setForegroundColor
+    //ts.setBackgroundColor
+    //ts.setDecoration(TextDecoration decoration);
+    //ts.setDecorationMode(TextDecorationMode mode);
+    //ts.setDecorationStyle(TextDecorationStyle style);
+    //ts.setDecorationColor(SkColor color);
+    //ts.setDecorationThicknessMultiplier(SkScalar m);
+    //ts.setFontStyle
+    //ts.addShadow
+    //ts.addFontFeature
+    //ts.setFontSize
+    //ts.setHeight
+    //ts.setHeightOverride
+    //ts.setletterSpacing
+    //ts.setWordSpacing
+    //ts.setTypeface
+    //ts.setLocale
+    //ts.setTextBaseline
+    //ts.setPlaceholder
+
+    builder->pushStyle(ts);
+}
+void RemoveStyle(ParagraphBuilder* builder, Fuzz*  fuzz) {
+    bool pop;
+    fuzz->next(&pop);
+    if (pop) {
+        builder->pop();
+    }
+}
+
+void AddStyleAndText(ParagraphBuilder* builder, Fuzz*  fuzz) {
+    AddStyle(builder, fuzz);
+    uint8_t text_type;
+    fuzz->next(&text_type);
+    switch (text_type % 3) {
+        case 0:
+            AddASCIIText(builder, fuzz);
+            break;
+        case 1:
+            AddUnicodeText(builder, fuzz);
+            break;
+        case 2:
+            AddZalgoText(builder, fuzz);
+            break;
+    }
+    RemoveStyle(builder, fuzz);
+
+}
+
+ParagraphStyle BuildParagraphStyle(Fuzz* fuzz) {
+    ParagraphStyle ps;
+    bool hinting;
+    fuzz->next(&hinting);
+    if (hinting) {
+        ps.turnHintingOff();
+    }
+    StrutStyle ss;
+    // TODO(westont): Fuzz this object.
+    ps.setStrutStyle(ss);
+    TextDirection td;
+    fuzz->nextEnum(&td, TextDirection::kRtl);
+    ps.setTextDirection(td);
+    TextAlign ta;
+    fuzz->nextEnum(&ta, TextAlign::kEnd);
+    ps.setTextAlign(ta);
+    size_t ml;
+    fuzz->next(&ml);
+    ps.setMaxLines(ml);
+    // TODO(westont): Randomize with other values and no value.
+    ps.setEllipsis(u"\u2026");
+    SkScalar h;
+    fuzz->next(&h);
+    ps.setHeight(h);
+    TextHeightBehavior thb = TextHeightBehavior::kAll;
+    // TODO(westont): This crashes our seed test case, why?
+    //fuzz->nextEnum(&thb, TextHeightBehavior::kDisableAll);
+    ps.setTextHeightBehavior(thb);
+
+    return ps;
+}
+
+}  // namespace
+
+DEF_FUZZ(api_skparagraph, fuzz) {
+    static sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
+    ParagraphStyle paragraph_style = BuildParagraphStyle(fuzz);
+    ParagraphBuilderImpl builder(paragraph_style, fontCollection);
+
+    uint8_t iterations;
+    fuzz->nextRange(&iterations, 1, MAX_TEXT_ADDITIONS);
+    for (int i = 0; i < iterations; i++) {
+        AddStyleAndText(&builder, fuzz);
+    }
+    // TODO(westont): Figure out if we can get more converage by having fontsFound, current
+    // they're not.
+    // if (!fontCollection->fontsFound()) return;
+
+    builder.pop();
+    auto paragraph = builder.Build();
+
+    paragraph->layout(TEST_CANVAS_WIDTH);
+
+}