| /* | |
| * Copyright 2020 Google LLC | |
| * | |
| * 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/SkFontMgr.h" | |
| #include "include/core/SkFontStyle.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 "modules/skshaper/utils/FactoryHelpers.h" | |
| #include "src/core/SkOSFile.h" | |
| #include "src/utils/SkOSPath.h" | |
| #include "tests/Test.h" | |
| #include "tools/Resources.h" | |
| #include "tools/fonts/FontToolUtils.h" | |
| #include <string.h> | |
| #include <algorithm> | |
| #include <limits> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #if defined(SK_ENABLE_PARAGRAPH) | |
| 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; | |
| sk_sp<SkFontMgr> mgr = ToolUtils::TestFontMgr(); | |
| 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(mgr->makeFromFile(file_path.c_str(), 0)); | |
| } | |
| 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; | |
| } | |
| static sk_sp<SkUnicode> get_unicode() { | |
| auto factory = SkShapers::BestAvailable(); | |
| return sk_ref_sp<SkUnicode>(factory->getUnicode()); | |
| } | |
| } // namespace | |
| DEF_FUZZ(SkParagraph, fuzz) { | |
| static sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>(); | |
| ParagraphStyle paragraph_style = BuildParagraphStyle(fuzz); | |
| ParagraphBuilderImpl builder(paragraph_style, fontCollection, get_unicode()); | |
| 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); | |
| } | |
| #endif // SK_ENABLE_PARAGRAPH |