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