• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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