/src/skia/fuzz/FuzzSkParagraph.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2020 Google Inc. |
3 | | * |
4 | | * Use of this source code is governed by a BSD-style license that can be |
5 | | * found in the LICENSE file. |
6 | | */ |
7 | | |
8 | | #include "fuzz/Fuzz.h" |
9 | | #include "fuzz/FuzzCommon.h" |
10 | | #include "include/core/SkBitmap.h" |
11 | | #include "include/core/SkCanvas.h" |
12 | | #include "include/core/SkColor.h" |
13 | | #include "include/core/SkFontMgr.h" |
14 | | #include "include/core/SkFontStyle.h" |
15 | | #include "include/core/SkPaint.h" |
16 | | #include "include/core/SkPoint.h" |
17 | | #include "include/core/SkRect.h" |
18 | | #include "include/core/SkRefCnt.h" |
19 | | #include "include/core/SkScalar.h" |
20 | | #include "include/core/SkSpan.h" |
21 | | #include "include/core/SkStream.h" |
22 | | #include "include/core/SkString.h" |
23 | | #include "include/core/SkTypeface.h" |
24 | | #include "include/core/SkTypes.h" |
25 | | #include "modules/skparagraph/include/DartTypes.h" |
26 | | #include "modules/skparagraph/include/FontCollection.h" |
27 | | #include "modules/skparagraph/include/Paragraph.h" |
28 | | #include "modules/skparagraph/include/ParagraphCache.h" |
29 | | #include "modules/skparagraph/include/ParagraphStyle.h" |
30 | | #include "modules/skparagraph/include/TextShadow.h" |
31 | | #include "modules/skparagraph/include/TextStyle.h" |
32 | | #include "modules/skparagraph/include/TypefaceFontProvider.h" |
33 | | #include "modules/skparagraph/src/ParagraphBuilderImpl.h" |
34 | | #include "modules/skparagraph/src/ParagraphImpl.h" |
35 | | #include "modules/skparagraph/src/Run.h" |
36 | | #include "modules/skparagraph/src/TextLine.h" |
37 | | #include "modules/skparagraph/utils/TestFontCollection.h" |
38 | | #include "modules/skshaper/utils/FactoryHelpers.h" |
39 | | #include "src/core/SkOSFile.h" |
40 | | #include "src/utils/SkOSPath.h" |
41 | | #include "tests/Test.h" |
42 | | #include "tools/Resources.h" |
43 | | #include "tools/fonts/FontToolUtils.h" |
44 | | |
45 | | #include <string.h> |
46 | | #include <algorithm> |
47 | | #include <limits> |
48 | | #include <memory> |
49 | | #include <string> |
50 | | #include <utility> |
51 | | #include <vector> |
52 | | |
53 | | #if defined(SK_ENABLE_PARAGRAPH) |
54 | | |
55 | | using namespace skia::textlayout; |
56 | | namespace { |
57 | | const uint8_t MAX_TEXT_LENGTH = 255; |
58 | | const uint8_t MAX_TEXT_ADDITIONS = 4; |
59 | | // Use 250 so uint8 can create text and layout width larger than the canvas. |
60 | | const uint16_t TEST_CANVAS_DIM = 250; |
61 | | |
62 | | class ResourceFontCollection : public FontCollection { |
63 | | public: |
64 | | ResourceFontCollection(bool testOnly = false) |
65 | | : fFontsFound(false) |
66 | | , fResolvedFonts(0) |
67 | | , fResourceDir(GetResourcePath("fonts").c_str()) |
68 | 0 | , fFontProvider(sk_make_sp<TypefaceFontProvider>()) { |
69 | 0 | std::vector<SkString> fonts; |
70 | 0 | SkOSFile::Iter iter(fResourceDir.c_str()); |
71 | |
|
72 | 0 | SkString path; |
73 | 0 | sk_sp<SkFontMgr> mgr = ToolUtils::TestFontMgr(); |
74 | 0 | while (iter.next(&path)) { |
75 | 0 | if (path.endsWith("Roboto-Italic.ttf")) { |
76 | 0 | fFontsFound = true; |
77 | 0 | } |
78 | 0 | fonts.emplace_back(path); |
79 | 0 | } |
80 | |
|
81 | 0 | if (!fFontsFound) { |
82 | | // SkDebugf("Fonts not found, skipping all the tests\n"); |
83 | 0 | return; |
84 | 0 | } |
85 | | // Only register fonts if we have to |
86 | 0 | for (auto& font : fonts) { |
87 | 0 | SkString file_path; |
88 | 0 | file_path.printf("%s/%s", fResourceDir.c_str(), font.c_str()); |
89 | 0 | fFontProvider->registerTypeface(mgr->makeFromFile(file_path.c_str(), 0)); |
90 | 0 | } |
91 | |
|
92 | 0 | if (testOnly) { |
93 | 0 | this->setTestFontManager(std::move(fFontProvider)); |
94 | 0 | } else { |
95 | 0 | this->setAssetFontManager(std::move(fFontProvider)); |
96 | 0 | } |
97 | 0 | this->disableFontFallback(); |
98 | 0 | } |
99 | | |
100 | 0 | size_t resolvedFonts() const { return fResolvedFonts; } |
101 | | |
102 | | // TODO: temp solution until we check in fonts |
103 | 0 | bool fontsFound() const { return fFontsFound; } |
104 | | |
105 | | private: |
106 | | bool fFontsFound; |
107 | | size_t fResolvedFonts; |
108 | | std::string fResourceDir; |
109 | | sk_sp<TypefaceFontProvider> fFontProvider; |
110 | | }; |
111 | | |
112 | | // buffer must be at least MAX_TEXT_LENGTH in length. |
113 | | // Returns size of text placed in buffer. |
114 | | template <typename T> |
115 | 0 | uint8_t RandomText(T* buffer, Fuzz* fuzz) { |
116 | 0 | uint8_t text_length; |
117 | 0 | fuzz->nextRange(&text_length, 0, MAX_TEXT_LENGTH); |
118 | 0 | fuzz->nextN(buffer, text_length); |
119 | 0 | return text_length; |
120 | 0 | } Unexecuted instantiation: FuzzSkParagraph.cpp:unsigned char (anonymous namespace)::RandomText<char>(char*, Fuzz*) Unexecuted instantiation: FuzzSkParagraph.cpp:unsigned char (anonymous namespace)::RandomText<char16_t>(char16_t*, Fuzz*) |
121 | | |
122 | | // Add random bytes to the paragraph. |
123 | 0 | void AddASCIIText(ParagraphBuilder* builder,Fuzz* fuzz) { |
124 | 0 | char text[MAX_TEXT_LENGTH]; |
125 | 0 | const auto text_length = RandomText(text, fuzz); |
126 | 0 | builder->addText(text, text_length); |
127 | 0 | } |
128 | | // Add random bytes to the paragraph. |
129 | 0 | void AddUnicodeText(ParagraphBuilder* builder,Fuzz* fuzz) { |
130 | 0 | char16_t text[MAX_TEXT_LENGTH]; |
131 | 0 | const auto text_length = RandomText(text, fuzz); |
132 | 0 | builder->addText(std::u16string(text, text_length)); |
133 | 0 | } |
134 | | |
135 | | // Combining characters to produce 'Zalgo' text. |
136 | | 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"; |
137 | | 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"; |
138 | | 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"; |
139 | | // Add random Zalgo text to the paragraph. |
140 | 0 | void AddZalgoText(ParagraphBuilder* builder, Fuzz* fuzz) { |
141 | 0 | char text[MAX_TEXT_LENGTH]; |
142 | 0 | const auto text_length = RandomText(text, fuzz); |
143 | 0 | std::u16string result; |
144 | |
|
145 | 0 | for (auto& c : std::string(text, text_length)) { |
146 | 0 | result += c; |
147 | 0 | uint8_t mark_count; |
148 | 0 | fuzz->next(&mark_count); |
149 | 0 | for (int i = 0; i < mark_count; i++) { |
150 | 0 | uint8_t mark_type, mark_index; |
151 | 0 | fuzz->next(&mark_type, &mark_index); |
152 | 0 | switch (mark_type % 3) { |
153 | 0 | case 0: |
154 | 0 | result += COMBINING_UP[mark_index % COMBINING_UP.size()]; |
155 | 0 | break; |
156 | 0 | case 1: |
157 | 0 | result += COMBINING_MIDDLE[mark_index % COMBINING_MIDDLE.size()]; |
158 | 0 | break; |
159 | 0 | case 2: |
160 | 0 | default: |
161 | 0 | result += COMBINING_DOWN[mark_index % COMBINING_DOWN.size()]; |
162 | 0 | break; |
163 | 0 | } |
164 | 0 | } |
165 | 0 | } |
166 | 0 | builder->addText(result); |
167 | 0 | } |
168 | | |
169 | 0 | void AddStyle(ParagraphBuilder* builder, Fuzz* fuzz) { |
170 | | // TODO(westont): Make this probabilistic, and fill in the remaining TextStyle fields. |
171 | 0 | TextStyle ts; |
172 | 0 | ts.setFontFamilies({SkString("Roboto")}); |
173 | | //ts.setColor(SK_ColorBLACK); |
174 | | //ts.setForegroundColor |
175 | | //ts.setBackgroundColor |
176 | | //ts.setDecoration(TextDecoration decoration); |
177 | | //ts.setDecorationMode(TextDecorationMode mode); |
178 | | //ts.setDecorationStyle(TextDecorationStyle style); |
179 | | //ts.setDecorationColor(SkColor color); |
180 | | //ts.setDecorationThicknessMultiplier(SkScalar m); |
181 | | //ts.setFontStyle |
182 | | //ts.addShadow |
183 | | //ts.addFontFeature |
184 | | //ts.setFontSize |
185 | | //ts.setHeight |
186 | | //ts.setHeightOverride |
187 | | //ts.setletterSpacing |
188 | | //ts.setWordSpacing |
189 | | //ts.setTypeface |
190 | | //ts.setLocale |
191 | | //ts.setTextBaseline |
192 | | //ts.setPlaceholder |
193 | |
|
194 | 0 | builder->pushStyle(ts); |
195 | 0 | } |
196 | 0 | void RemoveStyle(ParagraphBuilder* builder, Fuzz* fuzz) { |
197 | 0 | bool pop; |
198 | 0 | fuzz->next(&pop); |
199 | 0 | if (pop) { |
200 | 0 | builder->pop(); |
201 | 0 | } |
202 | 0 | } |
203 | | |
204 | 0 | void AddStyleAndText(ParagraphBuilder* builder, Fuzz* fuzz) { |
205 | 0 | AddStyle(builder, fuzz); |
206 | 0 | uint8_t text_type; |
207 | 0 | fuzz->next(&text_type); |
208 | 0 | switch (text_type % 3) { |
209 | 0 | case 0: |
210 | 0 | AddASCIIText(builder, fuzz); |
211 | 0 | break; |
212 | 0 | case 1: |
213 | 0 | AddUnicodeText(builder, fuzz); |
214 | 0 | break; |
215 | 0 | case 2: |
216 | 0 | AddZalgoText(builder, fuzz); |
217 | 0 | break; |
218 | 0 | } |
219 | 0 | RemoveStyle(builder, fuzz); |
220 | |
|
221 | 0 | } |
222 | | |
223 | 0 | ParagraphStyle BuildParagraphStyle(Fuzz* fuzz) { |
224 | 0 | ParagraphStyle ps; |
225 | 0 | bool hinting; |
226 | 0 | fuzz->next(&hinting); |
227 | 0 | if (hinting) { |
228 | 0 | ps.turnHintingOff(); |
229 | 0 | } |
230 | 0 | StrutStyle ss; |
231 | | // TODO(westont): Fuzz this object. |
232 | 0 | ps.setStrutStyle(ss); |
233 | 0 | TextDirection td; |
234 | 0 | fuzz->nextEnum(&td, TextDirection::kRtl); |
235 | 0 | ps.setTextDirection(td); |
236 | 0 | TextAlign ta; |
237 | 0 | fuzz->nextEnum(&ta, TextAlign::kEnd); |
238 | 0 | ps.setTextAlign(ta); |
239 | 0 | size_t ml; |
240 | 0 | fuzz->next(&ml); |
241 | 0 | ps.setMaxLines(ml); |
242 | | // TODO(westont): Randomize with other values and no value. |
243 | 0 | ps.setEllipsis(u"\u2026"); |
244 | 0 | SkScalar h; |
245 | 0 | fuzz->next(&h); |
246 | 0 | ps.setHeight(h); |
247 | 0 | TextHeightBehavior thb = TextHeightBehavior::kAll; |
248 | | // TODO(westont): This crashes our seed test case, why? |
249 | | //fuzz->nextEnum(&thb, TextHeightBehavior::kDisableAll); |
250 | 0 | ps.setTextHeightBehavior(thb); |
251 | |
|
252 | 0 | return ps; |
253 | 0 | } |
254 | | |
255 | 0 | static sk_sp<SkUnicode> get_unicode() { |
256 | 0 | auto factory = SkShapers::BestAvailable(); |
257 | 0 | return sk_ref_sp<SkUnicode>(factory->getUnicode()); |
258 | 0 | } |
259 | | |
260 | | } // namespace |
261 | | |
262 | 0 | DEF_FUZZ(SkParagraph, fuzz) { |
263 | 0 | static sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>(); |
264 | 0 | ParagraphStyle paragraph_style = BuildParagraphStyle(fuzz); |
265 | 0 | ParagraphBuilderImpl builder(paragraph_style, fontCollection, get_unicode()); |
266 | |
|
267 | 0 | uint8_t iterations; |
268 | 0 | fuzz->nextRange(&iterations, 1, MAX_TEXT_ADDITIONS); |
269 | 0 | for (int i = 0; i < iterations; i++) { |
270 | 0 | AddStyleAndText(&builder, fuzz); |
271 | 0 | } |
272 | | // TODO(westont): Figure out if we can get more converage by having fontsFound, current |
273 | | // they're not. |
274 | | // if (!fontCollection->fontsFound()) return; |
275 | |
|
276 | 0 | builder.pop(); |
277 | 0 | auto paragraph = builder.Build(); |
278 | |
|
279 | 0 | SkBitmap bm; |
280 | 0 | if (!bm.tryAllocN32Pixels(TEST_CANVAS_DIM, TEST_CANVAS_DIM)) { |
281 | 0 | return; |
282 | 0 | } |
283 | 0 | SkCanvas canvas(bm); |
284 | 0 | uint8_t layout_width; |
285 | 0 | fuzz->next(&layout_width); |
286 | 0 | paragraph->layout(layout_width); |
287 | 0 | paragraph->paint(&canvas, 0, 0); |
288 | 0 | } |
289 | | |
290 | | #endif // SK_ENABLE_PARAGRAPH |