|  | /* | 
|  | * 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/SkSpan.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/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; | 
|  | // Use 250 so uint8 can create text and layout width larger than the canvas. | 
|  | const uint16_t TEST_CANVAS_DIM = 250; | 
|  |  | 
|  | 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(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(); | 
|  |  | 
|  | SkBitmap bm; | 
|  | if (!bm.tryAllocN32Pixels(TEST_CANVAS_DIM, TEST_CANVAS_DIM)) { | 
|  | return; | 
|  | } | 
|  | SkCanvas canvas(bm); | 
|  | uint8_t layout_width; | 
|  | fuzz->next(&layout_width); | 
|  | paragraph->layout(layout_width); | 
|  | paragraph->paint(&canvas, 0, 0); | 
|  | } |