xref: /aosp_15_r20/external/skia/modules/skottie/tests/Text.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 <unordered_map>
9 
10 #include "include/core/SkFontMgr.h"
11 #include "include/core/SkFontStyle.h"
12 #include "include/core/SkStream.h"
13 #include "modules/skottie/include/Skottie.h"
14 #include "modules/skottie/include/SkottieProperty.h"
15 #include "modules/skshaper/include/SkShaper_factory.h"
16 #include "modules/skshaper/utils/FactoryHelpers.h"
17 #include "tests/Test.h"
18 #include "tools/ToolUtils.h"
19 #include "tools/fonts/FontToolUtils.h"
20 
21 using namespace skottie;
22 
23 namespace {
24 
25 class RecordMatchFamilyStyleSkFontMgr : public SkFontMgr {
26 public:
styleRequestedWhenMatchingFamily(const char * family) const27     const SkFontStyle* styleRequestedWhenMatchingFamily(const char* family) const {
28         auto s = fStyleRequestedWhenMatchingFamily.find(family);
29         return s != fStyleRequestedWhenMatchingFamily.end() ? &s->second : nullptr;
30     }
31 
32 private:
onCountFamilies() const33     int onCountFamilies() const override { return 0; }
onGetFamilyName(int index,SkString * familyName) const34     void onGetFamilyName(int index, SkString* familyName) const override {}
onCreateStyleSet(int index) const35     sk_sp<SkFontStyleSet> onCreateStyleSet(int index) const override { return nullptr; }
36 
onMatchFamily(const char[]) const37     sk_sp<SkFontStyleSet> onMatchFamily(const char[]) const override { return nullptr; }
38 
onMatchFamilyStyle(const char family[],const SkFontStyle & style) const39     sk_sp<SkTypeface> onMatchFamilyStyle(const char family[],
40                                          const SkFontStyle& style) const override {
41         fStyleRequestedWhenMatchingFamily[family] = style;
42         return nullptr;
43     }
onMatchFamilyStyleCharacter(const char familyName[],const SkFontStyle & style,const char * bcp47[],int bcp47Count,SkUnichar character) const44     sk_sp<SkTypeface> onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle& style,
45                                                   const char* bcp47[], int bcp47Count,
46                                                   SkUnichar character) const override {
47         fStyleRequestedWhenMatchingFamily[familyName] = style;
48         return nullptr;
49     }
50 
onMakeFromData(sk_sp<SkData>,int ttcIndex) const51     sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override {
52         return nullptr;
53     }
onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,int ttcIndex) const54     sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
55                                             int ttcIndex) const override {
56         return nullptr;
57     }
onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,const SkFontArguments &) const58     sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
59                                            const SkFontArguments&) const override {
60         return nullptr;
61     }
onMakeFromFile(const char path[],int ttcIndex) const62     sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
63         return nullptr;
64     }
65 
onLegacyMakeTypeface(const char familyName[],SkFontStyle) const66     sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[], SkFontStyle) const override {
67         return nullptr;
68     }
69 
70     mutable std::unordered_map<std::string, SkFontStyle> fStyleRequestedWhenMatchingFamily;
71 };
72 
73 } // namespace
74 
75 // This test relies on Skottie internals/implementation details, and may need to
76 // be updated in the future, if Skottie font resolution changes.
DEF_TEST(Skottie_Text_Style,r)77 DEF_TEST(Skottie_Text_Style, r) {
78     static constexpr char json[] =
79         R"({
80              "v": "5.2.1",
81              "w": 100,
82              "h": 100,
83              "fr": 10,
84              "ip": 0,
85              "op": 100,
86              "fonts": {
87                "list": [
88                  { "fName"  : "f1", "fFamily": "f1", "fStyle" : "Regular"   },
89                  { "fName"  : "f2", "fFamily": "f2", "fStyle" : "Medium"    },
90                  { "fName"  : "f3", "fFamily": "f3", "fStyle" : "Bold"      },
91                  { "fName"  : "f4", "fFamily": "f4", "fStyle" : "Light"     },
92                  { "fName"  : "f5", "fFamily": "f5", "fStyle" : "Extra"     },
93                  { "fName"  : "f6", "fFamily": "f6", "fStyle" : "ExtraBold" },
94 
95                  { "fName"  : "f7" , "fFamily": "f7" , "fStyle" : "Regular Italic"    },
96                  { "fName"  : "f8" , "fFamily": "f8" , "fStyle" : "Medium Italic"     },
97                  { "fName"  : "f9" , "fFamily": "f9" , "fStyle" : "Bold Italic"       },
98                  { "fName"  : "f10", "fFamily": "f10", "fStyle" : "Light Oblique"     },
99                  { "fName"  : "f11", "fFamily": "f11", "fStyle" : "Extra Oblique"     },
100                  { "fName"  : "f12", "fFamily": "f12", "fStyle" : "Extrabold Oblique" },
101 
102                  { "fName"  : "f13", "fFamily": "f13", "fStyle" : "Italic"  },
103                  { "fName"  : "f14", "fFamily": "f14", "fStyle" : "Oblique" },
104                  { "fName"  : "f15", "fFamily": "f15", "fStyle" : ""        }
105                ]
106              }
107            })";
108 
109     SkMemoryStream stream(json, strlen(json));
110     auto fmgr = sk_make_sp<RecordMatchFamilyStyleSkFontMgr>();
111 
112     auto anim = Animation::Builder()
113                     .setFontManager(fmgr)
114                     .setTextShapingFactory(SkShapers::BestAvailable())
115                     .make(&stream);
116 
117     REPORTER_ASSERT(r, anim);
118 
119     static constexpr struct {
120         const char*         family;
121         SkFontStyle::Weight weight;
122         SkFontStyle::Slant  slant;
123     } expected[] = {
124         { "f1" , SkFontStyle::kNormal_Weight   , SkFontStyle::kUpright_Slant },
125         { "f2" , SkFontStyle::kMedium_Weight   , SkFontStyle::kUpright_Slant },
126         { "f3" , SkFontStyle::kBold_Weight     , SkFontStyle::kUpright_Slant },
127         { "f4" , SkFontStyle::kLight_Weight    , SkFontStyle::kUpright_Slant },
128         { "f5" , SkFontStyle::kExtraBold_Weight, SkFontStyle::kUpright_Slant },
129         { "f6" , SkFontStyle::kExtraBold_Weight, SkFontStyle::kUpright_Slant },
130 
131         { "f7" , SkFontStyle::kNormal_Weight   , SkFontStyle::kItalic_Slant  },
132         { "f8" , SkFontStyle::kMedium_Weight   , SkFontStyle::kItalic_Slant  },
133         { "f9" , SkFontStyle::kBold_Weight     , SkFontStyle::kItalic_Slant  },
134         { "f10", SkFontStyle::kLight_Weight    , SkFontStyle::kOblique_Slant },
135         { "f11", SkFontStyle::kExtraBold_Weight, SkFontStyle::kOblique_Slant },
136         { "f12", SkFontStyle::kExtraBold_Weight, SkFontStyle::kOblique_Slant },
137 
138         { "f13", SkFontStyle::kNormal_Weight   , SkFontStyle::kItalic_Slant  },
139         { "f14", SkFontStyle::kNormal_Weight   , SkFontStyle::kOblique_Slant },
140         { "f15", SkFontStyle::kNormal_Weight   , SkFontStyle::kUpright_Slant },
141     };
142 
143     for (const auto& exp : expected) {
144         const auto* style = fmgr->styleRequestedWhenMatchingFamily(exp.family);
145         REPORTER_ASSERT(r, style);
146         REPORTER_ASSERT(r, style->weight() == exp.weight);
147         REPORTER_ASSERT(r, style->slant () == exp.slant );
148     }
149 }
150 
DEF_TEST(Skottie_Text_LayoutError,r)151 DEF_TEST(Skottie_Text_LayoutError, r) {
152     // Text node properties:
153     //   - scale to fit
154     //   - box width: 100
155     //   - min font size: 70
156     //   - string: Foo Bar Baz
157     //
158     // Layout should fail with these unsatisfiable constraints.
159     static constexpr char json[] =
160         R"({
161              "v": "5.2.1",
162              "w": 100,
163              "h": 100,
164              "fr": 10,
165              "ip": 0,
166              "op": 100,
167              "fonts": {
168                "list": [{
169                  "fFamily": "Arial",
170                  "fName": "Arial",
171                  "fStyle": "Bold"
172                }]
173              },
174              "layers": [{
175                "ty": 5,
176                "t": {
177                  "d": {
178                    "k": [{
179                      "t": 0,
180                      "s": {
181                        "f": "Arial",
182                        "t": "Foo Bar Baz",
183                        "s": 24,
184                        "fc": [1,1,1,1],
185                        "lh": 70,
186                        "ps": [0, 0],
187                        "sz": [100, 100],
188                        "mf": 70,
189                        "rs": 1
190                      }
191                    }]
192                  }
193                }
194              }]
195            })";
196 
197     class Logger final : public skottie::Logger {
198     public:
199         const std::vector<SkString>& errors() const { return fErrors; }
200 
201     private:
202         void log(Level lvl, const char message[], const char* = nullptr) override {
203             if (lvl == Level::kError) {
204                 fErrors.emplace_back(message);
205             }
206         }
207 
208         std::vector<SkString> fErrors;
209     };
210 
211     class PortableRP final : public skresources::ResourceProvider {
212     private:
213         sk_sp<SkTypeface> loadTypeface(const char[], const char[]) const override {
214             return ToolUtils::CreatePortableTypeface("Serif", SkFontStyle());
215         }
216     };
217 
218     SkMemoryStream stream(json, strlen(json));
219     auto logger = sk_make_sp<Logger>();
220 
221     auto anim = Animation::Builder()
222                     .setLogger(logger)
223                     .setResourceProvider(sk_make_sp<PortableRP>())
224                     .make(&stream);
225 
226     REPORTER_ASSERT(r, anim);
227     REPORTER_ASSERT(r, logger->errors().size() == 1);
228     REPORTER_ASSERT(r, logger->errors()[0].startsWith("Text layout failed"));
229 }
230 
DEF_TEST(Skottie_Text_FontFamily,r)231 DEF_TEST(Skottie_Text_FontFamily, r) {
232     static constexpr char json[] =
233         R"({
234              "v": "5.2.1",
235              "w": 100,
236              "h": 100,
237              "fr": 10,
238              "ip": 0,
239              "op": 100,
240              "fonts": {
241                "list": [{
242                  "fFamily": "family_1",
243                  "fName": "Font1",
244                  "fStyle": "Bold"
245                }]
246              },
247              "layers": [{
248                "ty": 5,
249                "t": {
250                  "d": {
251                    "k": [{
252                      "t": 0,
253                      "s": {
254                        "f": "Font1",
255                        "t": "Foo Bar Baz",
256                        "s": 24,
257                        "fc": [1,1,1,1],
258                        "lh": 70,
259                        "ps": [0, 0],
260                        "sz": [100, 100],
261                        "mf": 70,
262                        "rs": 0
263                      }
264                    }]
265                  }
266                }
267              }]
268            })";
269 
270     class TextObserver final : public PropertyObserver {
271     public:
272         const std::unique_ptr<TextPropertyHandle>& text() const { return fText; }
273 
274     private:
275         void onTextProperty(const char node_name[],
276                             const LazyHandle<TextPropertyHandle>& lh) override {
277             SkASSERT(!fText); // only one text prop in test
278             fText = lh();
279         }
280         std::unique_ptr<TextPropertyHandle> fText;
281     };
282 
283     auto fmgr = sk_make_sp<RecordMatchFamilyStyleSkFontMgr>();
284     auto prop_observer = sk_make_sp<TextObserver>();
285 
286     SkMemoryStream stream(json, strlen(json));
287     auto anim = Animation::Builder()
288                     .setFontManager(fmgr)
289                     .setPropertyObserver(prop_observer)
290                     .setTextShapingFactory(SkShapers::BestAvailable())
291                     .make(&stream);
292     REPORTER_ASSERT(r, anim);
293 
294     // Original family name was passed to fontmgr.
295     const auto* style1 = fmgr->styleRequestedWhenMatchingFamily("family_1");
296     REPORTER_ASSERT(r, style1);
297 
298     const auto& text_handle = prop_observer->text();
299     REPORTER_ASSERT(r, text_handle);
300     auto txt = (*text_handle).get();
301     txt.fFontFamily = "family_2";
302     (*text_handle).set(txt);
303 
304     // Updated family name was passed to fontmgr.
305     const auto* style2 = fmgr->styleRequestedWhenMatchingFamily("family_2");
306     REPORTER_ASSERT(r, style2);
307 }
308