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 "modules/skottie/include/Skottie.h"
13 #include "modules/skottie/include/SkottieProperty.h"
14 #include "tests/Test.h"
15 #include "tools/ToolUtils.h"
16
17 using namespace skottie;
18
19 namespace {
20
21 class RecordMatchFamilyStyleSkFontMgr : public SkFontMgr {
22 public:
styleRequestedWhenMatchingFamily(const char * family) const23 const SkFontStyle* styleRequestedWhenMatchingFamily(const char* family) const {
24 auto s = fStyleRequestedWhenMatchingFamily.find(family);
25 return s != fStyleRequestedWhenMatchingFamily.end() ? &s->second : nullptr;
26 }
27
28 private:
onCountFamilies() const29 int onCountFamilies() const override { return 0; }
onGetFamilyName(int index,SkString * familyName) const30 void onGetFamilyName(int index, SkString* familyName) const override {}
onCreateStyleSet(int index) const31 SkFontStyleSet* onCreateStyleSet(int index) const override { return nullptr; }
32
onMatchFamily(const char[]) const33 SkFontStyleSet* onMatchFamily(const char[]) const override { return nullptr; }
34
onMatchFamilyStyle(const char family[],const SkFontStyle & style) const35 SkTypeface* onMatchFamilyStyle(const char family[], const SkFontStyle& style) const override {
36 SkASSERT(fStyleRequestedWhenMatchingFamily.find(family) ==
37 fStyleRequestedWhenMatchingFamily.end());
38 fStyleRequestedWhenMatchingFamily[family] = style;
39 return nullptr;
40 }
onMatchFamilyStyleCharacter(const char familyName[],const SkFontStyle &,const char * bcp47[],int bcp47Count,SkUnichar character) const41 SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&,
42 const char* bcp47[], int bcp47Count,
43 SkUnichar character) const override {
44 return nullptr;
45 }
46
onMakeFromData(sk_sp<SkData>,int ttcIndex) const47 sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override {
48 return nullptr;
49 }
onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,int ttcIndex) const50 sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
51 int ttcIndex) const override {
52 return nullptr;
53 }
onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,const SkFontArguments &) const54 sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
55 const SkFontArguments&) const override {
56 return nullptr;
57 }
onMakeFromFile(const char path[],int ttcIndex) const58 sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
59 return nullptr;
60 }
61
onLegacyMakeTypeface(const char familyName[],SkFontStyle) const62 sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[], SkFontStyle) const override {
63 return nullptr;
64 }
65
66 mutable std::unordered_map<std::string, SkFontStyle> fStyleRequestedWhenMatchingFamily;
67 };
68
69 } // namespace
70
71 // This test relies on Skottie internals/implementation details, and may need to
72 // be updated in the future, if Skottie font resolution changes.
DEF_TEST(Skottie_Text_Style,r)73 DEF_TEST(Skottie_Text_Style, r) {
74 static constexpr char json[] =
75 R"({
76 "v": "5.2.1",
77 "w": 100,
78 "h": 100,
79 "fr": 10,
80 "ip": 0,
81 "op": 100,
82 "fonts": {
83 "list": [
84 { "fName" : "f1", "fFamily": "f1", "fStyle" : "Regular" },
85 { "fName" : "f2", "fFamily": "f2", "fStyle" : "Medium" },
86 { "fName" : "f3", "fFamily": "f3", "fStyle" : "Bold" },
87 { "fName" : "f4", "fFamily": "f4", "fStyle" : "Light" },
88 { "fName" : "f5", "fFamily": "f5", "fStyle" : "Extra" },
89 { "fName" : "f6", "fFamily": "f6", "fStyle" : "ExtraBold" },
90
91 { "fName" : "f7" , "fFamily": "f7" , "fStyle" : "Regular Italic" },
92 { "fName" : "f8" , "fFamily": "f8" , "fStyle" : "Medium Italic" },
93 { "fName" : "f9" , "fFamily": "f9" , "fStyle" : "Bold Italic" },
94 { "fName" : "f10", "fFamily": "f10", "fStyle" : "Light Oblique" },
95 { "fName" : "f11", "fFamily": "f11", "fStyle" : "Extra Oblique" },
96 { "fName" : "f12", "fFamily": "f12", "fStyle" : "Extrabold Oblique" },
97
98 { "fName" : "f13", "fFamily": "f13", "fStyle" : "Italic" },
99 { "fName" : "f14", "fFamily": "f14", "fStyle" : "Oblique" },
100 { "fName" : "f15", "fFamily": "f15", "fStyle" : "" }
101 ]
102 }
103 })";
104
105 SkMemoryStream stream(json, strlen(json));
106 auto fmgr = sk_make_sp<RecordMatchFamilyStyleSkFontMgr>();
107
108 auto anim = Animation::Builder()
109 .setFontManager(fmgr)
110 .make(&stream);
111
112 REPORTER_ASSERT(r, anim);
113
114 static constexpr struct {
115 const char* family;
116 SkFontStyle::Weight weight;
117 SkFontStyle::Slant slant;
118 } expected[] = {
119 { "f1" , SkFontStyle::kNormal_Weight , SkFontStyle::kUpright_Slant },
120 { "f2" , SkFontStyle::kMedium_Weight , SkFontStyle::kUpright_Slant },
121 { "f3" , SkFontStyle::kBold_Weight , SkFontStyle::kUpright_Slant },
122 { "f4" , SkFontStyle::kLight_Weight , SkFontStyle::kUpright_Slant },
123 { "f5" , SkFontStyle::kExtraBold_Weight, SkFontStyle::kUpright_Slant },
124 { "f6" , SkFontStyle::kExtraBold_Weight, SkFontStyle::kUpright_Slant },
125
126 { "f7" , SkFontStyle::kNormal_Weight , SkFontStyle::kItalic_Slant },
127 { "f8" , SkFontStyle::kMedium_Weight , SkFontStyle::kItalic_Slant },
128 { "f9" , SkFontStyle::kBold_Weight , SkFontStyle::kItalic_Slant },
129 { "f10", SkFontStyle::kLight_Weight , SkFontStyle::kOblique_Slant },
130 { "f11", SkFontStyle::kExtraBold_Weight, SkFontStyle::kOblique_Slant },
131 { "f12", SkFontStyle::kExtraBold_Weight, SkFontStyle::kOblique_Slant },
132
133 { "f13", SkFontStyle::kNormal_Weight , SkFontStyle::kItalic_Slant },
134 { "f14", SkFontStyle::kNormal_Weight , SkFontStyle::kOblique_Slant },
135 { "f15", SkFontStyle::kNormal_Weight , SkFontStyle::kUpright_Slant },
136 };
137
138 for (const auto& exp : expected) {
139 const auto* style = fmgr->styleRequestedWhenMatchingFamily(exp.family);
140 REPORTER_ASSERT(r, style);
141 REPORTER_ASSERT(r, style->weight() == exp.weight);
142 REPORTER_ASSERT(r, style->slant () == exp.slant );
143 }
144 }
145
DEF_TEST(Skottie_Text_LayoutError,r)146 DEF_TEST(Skottie_Text_LayoutError, r) {
147 // Text node properties:
148 // - scale to fit
149 // - box width: 100
150 // - min font size: 70
151 // - string: Foo Bar Baz
152 //
153 // Layout should fail with these unsatisfiable constraints.
154 static constexpr char json[] =
155 R"({
156 "v": "5.2.1",
157 "w": 100,
158 "h": 100,
159 "fr": 10,
160 "ip": 0,
161 "op": 100,
162 "fonts": {
163 "list": [{
164 "fFamily": "Arial",
165 "fName": "Arial",
166 "fStyle": "Bold"
167 }]
168 },
169 "layers": [{
170 "ty": 5,
171 "t": {
172 "d": {
173 "k": [{
174 "t": 0,
175 "s": {
176 "f": "Arial",
177 "t": "Foo Bar Baz",
178 "s": 24,
179 "fc": [1,1,1,1],
180 "lh": 70,
181 "ps": [0, 0],
182 "sz": [100, 100],
183 "mf": 70,
184 "rs": 1
185 }
186 }]
187 }
188 }
189 }]
190 })";
191
192 class Logger final : public skottie::Logger {
193 public:
194 const std::vector<SkString>& errors() const { return fErrors; }
195
196 private:
197 void log(Level lvl, const char message[], const char* = nullptr) override {
198 if (lvl == Level::kError) {
199 fErrors.emplace_back(message);
200 }
201 }
202
203 std::vector<SkString> fErrors;
204 };
205
206 class PortableRP final : public skresources::ResourceProvider {
207 private:
208 sk_sp<SkTypeface> loadTypeface(const char[], const char[]) const override {
209 return ToolUtils::create_portable_typeface("Serif", SkFontStyle());
210 }
211 };
212
213 SkMemoryStream stream(json, strlen(json));
214 auto logger = sk_make_sp<Logger>();
215
216 auto anim = Animation::Builder()
217 .setLogger(logger)
218 .setResourceProvider(sk_make_sp<PortableRP>())
219 .make(&stream);
220
221 REPORTER_ASSERT(r, anim);
222 REPORTER_ASSERT(r, logger->errors().size() == 1);
223 REPORTER_ASSERT(r, logger->errors()[0].startsWith("Text layout failed"));
224 }
225