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