• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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