1 /*
2 * Copyright 2022 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 "include/core/SkFontMgr.h"
9 #include "modules/skottie/src/text/SkottieShaper.h"
10 #include "tests/Test.h"
11 #include "tools/ToolUtils.h"
12
13 using namespace skottie;
14
DEF_TEST(Skottie_Shaper_Clusters,r)15 DEF_TEST(Skottie_Shaper_Clusters, r) {
16 const SkString text("Foo \rbar \rBaz.");
17
18 auto check_clusters = [](skiatest::Reporter* r, const SkString& text, Shaper::Flags flags,
19 const std::vector<size_t>& expected_clusters) {
20 const Shaper::TextDesc desc = {
21 ToolUtils::create_portable_typeface("Serif", SkFontStyle()),
22 18,
23 0, 18,
24 18,
25 0,
26 0,
27 SkTextUtils::Align::kCenter_Align,
28 Shaper::VAlign::kTop,
29 Shaper::ResizePolicy::kNone,
30 Shaper::LinebreakPolicy::kParagraph,
31 Shaper::Direction::kLTR,
32 Shaper::Capitalization::kNone,
33 0,
34 flags
35 };
36 const auto result = Shaper::Shape(text, desc, SkRect::MakeWH(1000, 1000),
37 SkFontMgr::RefDefault());
38 REPORTER_ASSERT(r, !result.fFragments.empty());
39
40 size_t i = 0;
41 for (const auto& frag : result.fFragments) {
42 const auto& glyphs = frag.fGlyphs;
43
44 if (flags & Shaper::kClusters) {
45 REPORTER_ASSERT(r, glyphs.fClusters.size() == glyphs.fGlyphIDs.size());
46 }
47
48 for (const auto& utf_cluster : glyphs.fClusters) {
49 REPORTER_ASSERT(r, i < expected_clusters.size());
50 REPORTER_ASSERT(r, utf_cluster == expected_clusters[i++]);
51 }
52 }
53
54 REPORTER_ASSERT(r, i == expected_clusters.size());
55 };
56
57 check_clusters(r, text, Shaper::kNone, {});
58 check_clusters(r, text, Shaper::kFragmentGlyphs, {});
59 check_clusters(r, text, Shaper::kClusters,
60 {0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13});
61 check_clusters(r, text, (Shaper::Flags)(Shaper::kClusters | Shaper::kFragmentGlyphs),
62 {0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13});
63 }
64
DEF_TEST(Skottie_Shaper_HAlign,reporter)65 DEF_TEST(Skottie_Shaper_HAlign, reporter) {
66 auto typeface = SkTypeface::MakeDefault();
67 REPORTER_ASSERT(reporter, typeface);
68
69 static constexpr struct {
70 SkScalar text_size,
71 tolerance;
72 } kTestSizes[] = {
73 // These gross tolerances are required for the test to pass on NativeFonts bots.
74 // Might be worth investigating why we need so much slack.
75 { 5, 2.0f },
76 { 10, 2.0f },
77 { 15, 2.4f },
78 { 25, 4.4f },
79 };
80
81 static constexpr struct {
82 SkTextUtils::Align align;
83 SkScalar l_selector,
84 r_selector;
85 } kTestAligns[] = {
86 { SkTextUtils:: kLeft_Align, 0.0f, 1.0f },
87 { SkTextUtils::kCenter_Align, 0.5f, 0.5f },
88 { SkTextUtils:: kRight_Align, 1.0f, 0.0f },
89 };
90
91 const SkString text("Foo, bar.\rBaz.");
92 const SkPoint text_point = SkPoint::Make(100, 100);
93
94 for (const auto& tsize : kTestSizes) {
95 for (const auto& talign : kTestAligns) {
96 const skottie::Shaper::TextDesc desc = {
97 typeface,
98 tsize.text_size,
99 0, tsize.text_size,
100 tsize.text_size,
101 0,
102 0,
103 talign.align,
104 Shaper::VAlign::kTopBaseline,
105 Shaper::ResizePolicy::kNone,
106 Shaper::LinebreakPolicy::kExplicit,
107 Shaper::Direction::kLTR,
108 Shaper::Capitalization::kNone,
109 };
110
111 const auto shape_result = Shaper::Shape(text, desc, text_point,
112 SkFontMgr::RefDefault());
113 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
114 REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
115
116 const auto shape_bounds = shape_result.computeVisualBounds();
117 REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
118
119 const auto expected_l = text_point.x() - shape_bounds.width() * talign.l_selector;
120 REPORTER_ASSERT(reporter,
121 std::fabs(shape_bounds.left() - expected_l) < tsize.tolerance,
122 "%f %f %f %f %d", shape_bounds.left(), expected_l, tsize.tolerance,
123 tsize.text_size, talign.align);
124
125 const auto expected_r = text_point.x() + shape_bounds.width() * talign.r_selector;
126 REPORTER_ASSERT(reporter,
127 std::fabs(shape_bounds.right() - expected_r) < tsize.tolerance,
128 "%f %f %f %f %d", shape_bounds.right(), expected_r, tsize.tolerance,
129 tsize.text_size, talign.align);
130
131 }
132 }
133 }
134
DEF_TEST(Skottie_Shaper_VAlign,reporter)135 DEF_TEST(Skottie_Shaper_VAlign, reporter) {
136 auto typeface = SkTypeface::MakeDefault();
137 REPORTER_ASSERT(reporter, typeface);
138
139 static constexpr struct {
140 SkScalar text_size,
141 tolerance;
142 } kTestSizes[] = {
143 // These gross tolerances are required for the test to pass on NativeFonts bots.
144 // Might be worth investigating why we need so much slack.
145 { 5, 2.0f },
146 { 10, 4.0f },
147 { 15, 5.5f },
148 { 25, 8.0f },
149 };
150
151 struct {
152 skottie::Shaper::VAlign align;
153 SkScalar topFactor;
154 } kTestAligns[] = {
155 { skottie::Shaper::VAlign::kHybridTop , 0.0f },
156 { skottie::Shaper::VAlign::kHybridCenter, 0.5f },
157 // TODO: any way to test kTopBaseline?
158 };
159
160 const SkString text("Foo, bar.\rBaz.");
161 const auto text_box = SkRect::MakeXYWH(100, 100, 1000, 1000); // large-enough to avoid breaks.
162
163
164 for (const auto& tsize : kTestSizes) {
165 for (const auto& talign : kTestAligns) {
166 const skottie::Shaper::TextDesc desc = {
167 typeface,
168 tsize.text_size,
169 0, tsize.text_size,
170 tsize.text_size,
171 0,
172 0,
173 SkTextUtils::Align::kCenter_Align,
174 talign.align,
175 Shaper::ResizePolicy::kNone,
176 Shaper::LinebreakPolicy::kParagraph,
177 Shaper::Direction::kLTR,
178 Shaper::Capitalization::kNone,
179 };
180
181 const auto shape_result = Shaper::Shape(text, desc, text_box, SkFontMgr::RefDefault());
182 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
183 REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
184
185 const auto shape_bounds = shape_result.computeVisualBounds();
186 REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
187
188 const auto v_diff = text_box.height() - shape_bounds.height();
189
190 const auto expected_t = text_box.top() + v_diff * talign.topFactor;
191 REPORTER_ASSERT(reporter,
192 std::fabs(shape_bounds.top() - expected_t) < tsize.tolerance,
193 "%f %f %f %f %d", shape_bounds.top(), expected_t, tsize.tolerance,
194 tsize.text_size, SkToU32(talign.align));
195
196 const auto expected_b = text_box.bottom() - v_diff * (1 - talign.topFactor);
197 REPORTER_ASSERT(reporter,
198 std::fabs(shape_bounds.bottom() - expected_b) < tsize.tolerance,
199 "%f %f %f %f %d", shape_bounds.bottom(), expected_b, tsize.tolerance,
200 tsize.text_size, SkToU32(talign.align));
201 }
202 }
203 }
204
DEF_TEST(Skottie_Shaper_FragmentGlyphs,reporter)205 DEF_TEST(Skottie_Shaper_FragmentGlyphs, reporter) {
206 skottie::Shaper::TextDesc desc = {
207 SkTypeface::MakeDefault(),
208 18,
209 0, 18,
210 18,
211 0,
212 0,
213 SkTextUtils::Align::kCenter_Align,
214 Shaper::VAlign::kTop,
215 Shaper::ResizePolicy::kNone,
216 Shaper::LinebreakPolicy::kParagraph,
217 Shaper::Direction::kLTR,
218 Shaper::Capitalization::kNone,
219 };
220
221 const SkString text("Foo bar baz");
222 const auto text_box = SkRect::MakeWH(100, 100);
223
224 {
225 const auto shape_result = Shaper::Shape(text, desc, text_box, SkFontMgr::RefDefault());
226 // Default/consolidated mode => single blob result.
227 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
228 REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
229 }
230
231 {
232 desc.fFlags = Shaper::Flags::kFragmentGlyphs;
233 const auto shape_result = skottie::Shaper::Shape(text, desc, text_box,
234 SkFontMgr::RefDefault());
235 // Fragmented mode => one blob per glyph.
236 const size_t expectedSize = text.size();
237 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == expectedSize);
238 for (size_t i = 0; i < expectedSize; ++i) {
239 REPORTER_ASSERT(reporter, !shape_result.fFragments[i].fGlyphs.fRuns.empty());
240 }
241 }
242 }
243
244 #if defined(SK_SHAPER_HARFBUZZ_AVAILABLE) && !defined(SK_BUILD_FOR_WIN)
245
DEF_TEST(Skottie_Shaper_ExplicitFontMgr,reporter)246 DEF_TEST(Skottie_Shaper_ExplicitFontMgr, reporter) {
247 class CountingFontMgr : public SkFontMgr {
248 public:
249 size_t fallbackCount() const { return fFallbackCount; }
250
251 protected:
252 int onCountFamilies() const override { return 0; }
253 void onGetFamilyName(int index, SkString* familyName) const override {
254 SkDEBUGFAIL("onGetFamilyName called with bad index");
255 }
256 SkFontStyleSet* onCreateStyleSet(int index) const override {
257 SkDEBUGFAIL("onCreateStyleSet called with bad index");
258 return nullptr;
259 }
260 SkFontStyleSet* onMatchFamily(const char[]) const override {
261 return SkFontStyleSet::CreateEmpty();
262 }
263
264 SkTypeface* onMatchFamilyStyle(const char[], const SkFontStyle&) const override {
265 return nullptr;
266 }
267 SkTypeface* onMatchFamilyStyleCharacter(const char familyName[],
268 const SkFontStyle& style,
269 const char* bcp47[],
270 int bcp47Count,
271 SkUnichar character) const override {
272 fFallbackCount++;
273 return nullptr;
274 }
275
276 sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int) const override {
277 return nullptr;
278 }
279 sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>, int) const override {
280 return nullptr;
281 }
282 sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
283 const SkFontArguments&) const override {
284 return nullptr;
285 }
286 sk_sp<SkTypeface> onMakeFromFile(const char[], int) const override {
287 return nullptr;
288 }
289 sk_sp<SkTypeface> onLegacyMakeTypeface(const char [], SkFontStyle) const override {
290 return nullptr;
291 }
292 private:
293 mutable size_t fFallbackCount = 0;
294 };
295
296 auto fontmgr = sk_make_sp<CountingFontMgr>();
297
298 skottie::Shaper::TextDesc desc = {
299 ToolUtils::create_portable_typeface(),
300 18,
301 0, 18,
302 18,
303 0,
304 0,
305 SkTextUtils::Align::kCenter_Align,
306 Shaper::VAlign::kTop,
307 Shaper::ResizePolicy::kNone,
308 Shaper::LinebreakPolicy::kParagraph,
309 Shaper::Direction::kLTR,
310 Shaper::Capitalization::kNone,
311 };
312
313 const auto text_box = SkRect::MakeWH(100, 100);
314
315 {
316 const auto shape_result = Shaper::Shape(SkString("foo bar"), desc, text_box, fontmgr);
317
318 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
319 REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
320 REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 0ul);
321 REPORTER_ASSERT(reporter, shape_result.fMissingGlyphCount == 0);
322 }
323
324 {
325 // An unassigned codepoint should trigger fallback.
326 const auto shape_result = skottie::Shaper::Shape(SkString("foo\U000DFFFFbar"),
327 desc, text_box, fontmgr);
328
329 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
330 REPORTER_ASSERT(reporter, !shape_result.fFragments[0].fGlyphs.fRuns.empty());
331 REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 1ul);
332 REPORTER_ASSERT(reporter, shape_result.fMissingGlyphCount == 1ul);
333 }
334 }
335
336 #endif
337
338