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