1 /*
2 * Copyright 2018 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 "modules/skottie/src/SkottiePriv.h"
9
10 #include "include/core/SkData.h"
11 #include "include/core/SkFontMgr.h"
12 #include "include/core/SkTypes.h"
13 #include "modules/skottie/src/SkottieJson.h"
14 #include "modules/skottie/src/text/TextAdapter.h"
15 #include "modules/skottie/src/text/TextAnimator.h"
16 #include "modules/skottie/src/text/TextValue.h"
17 #include "modules/sksg/include/SkSGDraw.h"
18 #include "modules/sksg/include/SkSGGroup.h"
19 #include "modules/sksg/include/SkSGPaint.h"
20 #include "modules/sksg/include/SkSGText.h"
21
22 #include <string.h>
23
24 namespace skottie {
25 namespace internal {
26
27 namespace {
28
FontStyle(const AnimationBuilder * abuilder,const char * style)29 SkFontStyle FontStyle(const AnimationBuilder* abuilder, const char* style) {
30 static constexpr struct {
31 const char* fName;
32 const SkFontStyle::Weight fWeight;
33 } gWeightMap[] = {
34 { "Regular" , SkFontStyle::kNormal_Weight },
35 { "Medium" , SkFontStyle::kMedium_Weight },
36 { "Bold" , SkFontStyle::kBold_Weight },
37 { "Light" , SkFontStyle::kLight_Weight },
38 { "Black" , SkFontStyle::kBlack_Weight },
39 { "Thin" , SkFontStyle::kThin_Weight },
40 { "Extra" , SkFontStyle::kExtraBold_Weight },
41 { "ExtraBold" , SkFontStyle::kExtraBold_Weight },
42 { "ExtraLight", SkFontStyle::kExtraLight_Weight },
43 { "ExtraBlack", SkFontStyle::kExtraBlack_Weight },
44 { "SemiBold" , SkFontStyle::kSemiBold_Weight },
45 { "Hairline" , SkFontStyle::kThin_Weight },
46 { "Normal" , SkFontStyle::kNormal_Weight },
47 { "Plain" , SkFontStyle::kNormal_Weight },
48 { "Standard" , SkFontStyle::kNormal_Weight },
49 { "Roman" , SkFontStyle::kNormal_Weight },
50 { "Heavy" , SkFontStyle::kBlack_Weight },
51 { "Demi" , SkFontStyle::kSemiBold_Weight },
52 { "DemiBold" , SkFontStyle::kSemiBold_Weight },
53 { "Ultra" , SkFontStyle::kExtraBold_Weight },
54 { "UltraBold" , SkFontStyle::kExtraBold_Weight },
55 { "UltraBlack", SkFontStyle::kExtraBlack_Weight },
56 { "UltraHeavy", SkFontStyle::kExtraBlack_Weight },
57 { "UltraLight", SkFontStyle::kExtraLight_Weight },
58 };
59
60 SkFontStyle::Weight weight = SkFontStyle::kNormal_Weight;
61 for (const auto& w : gWeightMap) {
62 const auto name_len = strlen(w.fName);
63 if (!strncmp(style, w.fName, name_len)) {
64 weight = w.fWeight;
65 style += name_len;
66 break;
67 }
68 }
69
70 static constexpr struct {
71 const char* fName;
72 const SkFontStyle::Slant fSlant;
73 } gSlantMap[] = {
74 { "Italic" , SkFontStyle::kItalic_Slant },
75 { "Oblique", SkFontStyle::kOblique_Slant },
76 };
77
78 SkFontStyle::Slant slant = SkFontStyle::kUpright_Slant;
79 if (*style != '\0') {
80 for (const auto& s : gSlantMap) {
81 if (!strcmp(style, s.fName)) {
82 slant = s.fSlant;
83 style += strlen(s.fName);
84 break;
85 }
86 }
87 }
88
89 if (*style != '\0') {
90 abuilder->log(Logger::Level::kWarning, nullptr, "Unknown font style: %s.", style);
91 }
92
93 return SkFontStyle(weight, SkFontStyle::kNormal_Width, slant);
94 }
95
96 } // namespace
97
matches(const char family[],const char style[]) const98 bool AnimationBuilder::FontInfo::matches(const char family[], const char style[]) const {
99 return 0 == strcmp(fFamily.c_str(), family)
100 && 0 == strcmp(fStyle.c_str(), style);
101 }
102
parseFonts(const skjson::ObjectValue * jfonts,const skjson::ArrayValue * jchars)103 void AnimationBuilder::parseFonts(const skjson::ObjectValue* jfonts,
104 const skjson::ArrayValue* jchars) {
105 // Optional array of font entries, referenced (by name) from text layer document nodes. E.g.
106 // "fonts": {
107 // "list": [
108 // {
109 // "ascent": 75,
110 // "fClass": "",
111 // "fFamily": "Roboto",
112 // "fName": "Roboto-Regular",
113 // "fPath": "https://fonts.googleapis.com/css?family=Roboto",
114 // "fPath": "",
115 // "fStyle": "Regular",
116 // "fWeight": "",
117 // "origin": 1
118 // }
119 // ]
120 // },
121 if (jfonts) {
122 if (const skjson::ArrayValue* jlist = (*jfonts)["list"]) {
123 for (const skjson::ObjectValue* jfont : *jlist) {
124 if (!jfont) {
125 continue;
126 }
127
128 const skjson::StringValue* jname = (*jfont)["fName"];
129 const skjson::StringValue* jfamily = (*jfont)["fFamily"];
130 const skjson::StringValue* jstyle = (*jfont)["fStyle"];
131 const skjson::StringValue* jpath = (*jfont)["fPath"];
132
133 if (!jname || !jname->size() ||
134 !jfamily || !jfamily->size() ||
135 !jstyle || !jstyle->size()) {
136 this->log(Logger::Level::kError, jfont, "Invalid font.");
137 continue;
138 }
139
140 const auto& fmgr = fLazyFontMgr.get();
141
142 // Typeface fallback order:
143 // 1) externally-loaded font (provided by the embedder)
144 // 2) system font (family/style)
145 // 3) system default
146
147 sk_sp<SkTypeface> tf =
148 fmgr->makeFromData(fResourceProvider->loadFont(jname->begin(),
149 jpath ? jpath->begin()
150 : nullptr));
151
152 if (!tf) {
153 tf.reset(fmgr->matchFamilyStyle(jfamily->begin(),
154 FontStyle(this, jstyle->begin())));
155 }
156
157 if (!tf) {
158 this->log(Logger::Level::kError, nullptr,
159 "Could not create typeface for %s|%s.",
160 jfamily->begin(), jstyle->begin());
161 // Last resort.
162 tf = fmgr->legacyMakeTypeface(nullptr, FontStyle(this, jstyle->begin()));
163 if (!tf) {
164 continue;
165 }
166 }
167
168 fFonts.set(SkString(jname->begin(), jname->size()),
169 {
170 SkString(jfamily->begin(), jfamily->size()),
171 SkString(jstyle->begin(), jstyle->size()),
172 ParseDefault((*jfont)["ascent"] , 0.0f),
173 std::move(tf)
174 });
175 }
176 }
177 }
178
179 // Optional array of glyphs, to be associated with one of the declared fonts. E.g.
180 // "chars": [
181 // {
182 // "ch": "t",
183 // "data": {
184 // "shapes": [...]
185 // },
186 // "fFamily": "Roboto",
187 // "size": 50,
188 // "style": "Regular",
189 // "w": 32.67
190 // }
191 // ]
192 if (jchars) {
193 FontInfo* current_font = nullptr;
194
195 for (const skjson::ObjectValue* jchar : *jchars) {
196 if (!jchar) {
197 continue;
198 }
199
200 const skjson::StringValue* jch = (*jchar)["ch"];
201 if (!jch) {
202 continue;
203 }
204
205 const skjson::StringValue* jfamily = (*jchar)["fFamily"];
206 const skjson::StringValue* jstyle = (*jchar)["style"]; // "style", not "fStyle"...
207
208 const auto* ch_ptr = jch->begin();
209 const auto ch_len = jch->size();
210
211 if (!jfamily || !jstyle || (SkUTF::CountUTF8(ch_ptr, ch_len) != 1)) {
212 this->log(Logger::Level::kError, jchar, "Invalid glyph.");
213 continue;
214 }
215
216 const auto uni = SkUTF::NextUTF8(&ch_ptr, ch_ptr + ch_len);
217 SkASSERT(uni != -1);
218
219 const auto* family = jfamily->begin();
220 const auto* style = jstyle->begin();
221
222 // Locate (and cache) the font info. Unlike text nodes, glyphs reference the font by
223 // (family, style) -- not by name :( For now this performs a linear search over *all*
224 // fonts: generally there are few of them, and glyph definitions are font-clustered.
225 // If problematic, we can refactor as a two-level hashmap.
226 if (!current_font || !current_font->matches(family, style)) {
227 current_font = nullptr;
228 fFonts.foreach([&](const SkString& name, FontInfo* finfo) {
229 if (finfo->matches(family, style)) {
230 current_font = finfo;
231 // TODO: would be nice to break early here...
232 }
233 });
234 if (!current_font) {
235 this->log(Logger::Level::kError, nullptr,
236 "Font not found for codepoint (%d, %s, %s).", uni, family, style);
237 continue;
238 }
239 }
240
241 // TODO: parse glyphs
242 }
243 }
244 }
245
findFont(const SkString & font_name) const246 const AnimationBuilder::FontInfo* AnimationBuilder::findFont(const SkString& font_name) const {
247 return fFonts.find(font_name);
248 }
249
attachTextLayer(const skjson::ObjectValue & layer,LayerInfo *) const250 sk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectValue& layer,
251 LayerInfo*) const {
252 // General text node format:
253 // "t": {
254 // "a": [], // animators (see TextAnimator.cpp)
255 // "d": {
256 // "k": [
257 // {
258 // "s": {
259 // "f": "Roboto-Regular",
260 // "fc": [
261 // 0.42,
262 // 0.15,
263 // 0.15
264 // ],
265 // "j": 1,
266 // "lh": 60,
267 // "ls": 0,
268 // "s": 50,
269 // "t": "text align right",
270 // "tr": 0
271 // },
272 // "t": 0
273 // }
274 // ]
275 // },
276 // "m": {}, // "more options" (TODO)
277 // "p": {} // "path options" (TODO)
278 // },
279 const skjson::ObjectValue* jt = layer["t"];
280 if (!jt) {
281 this->log(Logger::Level::kError, &layer, "Missing text layer \"t\" property.");
282 return nullptr;
283 }
284
285 const skjson::ArrayValue* animated_props = (*jt)["a"];
286 const auto has_animators = (animated_props && animated_props->size() > 0);
287
288 const skjson::ObjectValue* jd = (*jt)["d"];
289 if (!jd) {
290 return nullptr;
291 }
292
293 auto text_root = sksg::Group::Make();
294 auto adapter = sk_make_sp<TextAdapter>(text_root,
295 fLazyFontMgr.getMaybeNull(),
296 fLogger,
297 has_animators);
298
299 this->bindProperty<TextValue>(*jd,
300 [adapter] (const TextValue& txt) {
301 adapter->setText(txt);
302 });
303
304 if (has_animators) {
305 if (auto alist = TextAnimatorList::Make(*animated_props, this, adapter)) {
306 fCurrentAnimatorScope->push_back(std::move(alist));
307 }
308 }
309
310 this->dispatchTextProperty(adapter);
311
312 return text_root;
313 }
314
315 } // namespace internal
316 } // namespace skottie
317