• 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/SkSGPath.h"
21 #include "modules/sksg/include/SkSGText.h"
22 #include "src/base/SkTSearch.h"
23 
24 #include <string.h>
25 
26 namespace skottie {
27 namespace internal {
28 
29 namespace {
30 
31 template <typename T, typename TMap>
parse_map(const TMap & map,const char * str,T * result)32 const char* parse_map(const TMap& map, const char* str, T* result) {
33     // ignore leading whitespace
34     while (*str == ' ') ++str;
35 
36     const char* next_tok = strchr(str, ' ');
37 
38     if (const auto len = next_tok ? (next_tok - str) : strlen(str)) {
39         for (const auto& e : map) {
40             const char* key = std::get<0>(e);
41             if (!strncmp(str, key, len) && key[len] == '\0') {
42                 *result = std::get<1>(e);
43                 return str + len;
44             }
45         }
46     }
47 
48     return str;
49 }
50 
FontStyle(const AnimationBuilder * abuilder,const char * style)51 SkFontStyle FontStyle(const AnimationBuilder* abuilder, const char* style) {
52     static constexpr std::tuple<const char*, SkFontStyle::Weight> gWeightMap[] = {
53         { "regular"   , SkFontStyle::kNormal_Weight     },
54         { "medium"    , SkFontStyle::kMedium_Weight     },
55         { "bold"      , SkFontStyle::kBold_Weight       },
56         { "light"     , SkFontStyle::kLight_Weight      },
57         { "black"     , SkFontStyle::kBlack_Weight      },
58         { "thin"      , SkFontStyle::kThin_Weight       },
59         { "extra"     , SkFontStyle::kExtraBold_Weight  },
60         { "extrabold" , SkFontStyle::kExtraBold_Weight  },
61         { "extralight", SkFontStyle::kExtraLight_Weight },
62         { "extrablack", SkFontStyle::kExtraBlack_Weight },
63         { "semibold"  , SkFontStyle::kSemiBold_Weight   },
64         { "hairline"  , SkFontStyle::kThin_Weight       },
65         { "normal"    , SkFontStyle::kNormal_Weight     },
66         { "plain"     , SkFontStyle::kNormal_Weight     },
67         { "standard"  , SkFontStyle::kNormal_Weight     },
68         { "roman"     , SkFontStyle::kNormal_Weight     },
69         { "heavy"     , SkFontStyle::kBlack_Weight      },
70         { "demi"      , SkFontStyle::kSemiBold_Weight   },
71         { "demibold"  , SkFontStyle::kSemiBold_Weight   },
72         { "ultra"     , SkFontStyle::kExtraBold_Weight  },
73         { "ultrabold" , SkFontStyle::kExtraBold_Weight  },
74         { "ultrablack", SkFontStyle::kExtraBlack_Weight },
75         { "ultraheavy", SkFontStyle::kExtraBlack_Weight },
76         { "ultralight", SkFontStyle::kExtraLight_Weight },
77     };
78     static constexpr std::tuple<const char*, SkFontStyle::Slant> gSlantMap[] = {
79         { "italic" , SkFontStyle::kItalic_Slant  },
80         { "oblique", SkFontStyle::kOblique_Slant },
81     };
82 
83     auto weight = SkFontStyle::kNormal_Weight;
84     auto slant  = SkFontStyle::kUpright_Slant;
85 
86     // style is case insensitive.
87     SkAutoAsciiToLC lc_style(style);
88     style = lc_style.lc();
89     style = parse_map(gWeightMap, style, &weight);
90     style = parse_map(gSlantMap , style, &slant );
91 
92     // ignore trailing whitespace
93     while (*style == ' ') ++style;
94 
95     if (*style) {
96         abuilder->log(Logger::Level::kWarning, nullptr, "Unknown font style: %s.", style);
97     }
98 
99     return SkFontStyle(weight, SkFontStyle::kNormal_Width, slant);
100 }
101 
102 } // namespace
103 
matches(const char family[],const char style[]) const104 bool AnimationBuilder::FontInfo::matches(const char family[], const char style[]) const {
105     return 0 == strcmp(fFamily.c_str(), family)
106         && 0 == strcmp(fStyle.c_str(), style);
107 }
108 
parseFonts(const skjson::ObjectValue * jfonts,const skjson::ArrayValue * jchars)109 void AnimationBuilder::parseFonts(const skjson::ObjectValue* jfonts,
110                                   const skjson::ArrayValue* jchars) {
111     // Optional array of font entries, referenced (by name) from text layer document nodes. E.g.
112     // "fonts": {
113     //        "list": [
114     //            {
115     //                "ascent": 75,
116     //                "fClass": "",
117     //                "fFamily": "Roboto",
118     //                "fName": "Roboto-Regular",
119     //                "fPath": "https://fonts.googleapis.com/css?family=Roboto",
120     //                "fPath": "",
121     //                "fStyle": "Regular",
122     //                "fWeight": "",
123     //                "origin": 1
124     //            }
125     //        ]
126     //    },
127     const skjson::ArrayValue* jlist = jfonts
128             ? static_cast<const skjson::ArrayValue*>((*jfonts)["list"])
129             : nullptr;
130     if (!jlist) {
131         return;
132     }
133 
134     // First pass: collect font info.
135     for (const skjson::ObjectValue* jfont : *jlist) {
136         if (!jfont) {
137             continue;
138         }
139 
140         const skjson::StringValue* jname   = (*jfont)["fName"];
141         const skjson::StringValue* jfamily = (*jfont)["fFamily"];
142         const skjson::StringValue* jstyle  = (*jfont)["fStyle"];
143         const skjson::StringValue* jpath   = (*jfont)["fPath"];
144 
145         if (!jname   || !jname->size() ||
146             !jfamily || !jfamily->size() ||
147             !jstyle) {
148             this->log(Logger::Level::kError, jfont, "Invalid font.");
149             continue;
150         }
151 
152         fFonts.set(SkString(jname->begin(), jname->size()),
153                   {
154                       SkString(jfamily->begin(), jfamily->size()),
155                       SkString( jstyle->begin(),  jstyle->size()),
156                       jpath ? SkString(  jpath->begin(),   jpath->size()) : SkString(),
157                       ParseDefault((*jfont)["ascent"] , 0.0f),
158                       nullptr, // placeholder
159                       CustomFont::Builder()
160                   });
161     }
162 
163     const auto has_comp_glyphs = [](const skjson::ArrayValue* jchars) {
164         if (!jchars) {
165             return false;
166         }
167 
168         for (const skjson::ObjectValue* jchar : *jchars) {
169             if (!jchar) {
170                 continue;
171             }
172             if (ParseDefault<int>((*jchar)["t"], 0) == 1) {
173                 return true;
174             }
175         }
176 
177         return false;
178     };
179 
180     // Historically, Skottie has been loading native fonts before embedded glyphs, unless
181     // the opposite is explicitly requested via kPreferEmbeddedFonts.  That's mostly because
182     // embedded glyphs used to be just a path representation of system fonts at export time,
183     // (and thus lower quality).
184     //
185     // OTOH embedded glyph *compositions* must be prioritized, as they are presumably more
186     // expressive than the system font equivalent.
187     const auto prioritize_embedded_fonts =
188             (fFlags & Animation::Builder::kPreferEmbeddedFonts) || has_comp_glyphs(jchars);
189 
190     // Optional pass.
191     if (jchars && prioritize_embedded_fonts && this->resolveEmbeddedTypefaces(*jchars)) {
192         return;
193     }
194 
195     // Native typeface resolution.
196     if (this->resolveNativeTypefaces()) {
197         return;
198     }
199 
200     // Embedded typeface fallback.
201     if (jchars && !prioritize_embedded_fonts) {
202         this->resolveEmbeddedTypefaces(*jchars);
203     }
204 }
205 
resolveNativeTypefaces()206 bool AnimationBuilder::resolveNativeTypefaces() {
207     bool has_unresolved = false;
208 
209     fFonts.foreach([&](const SkString& name, FontInfo* finfo) {
210         SkASSERT(finfo);
211 
212         if (finfo->fTypeface) {
213             // Already resolved from glyph paths.
214             return;
215         }
216 
217         const auto& fmgr = fLazyFontMgr.get();
218 
219         // Typeface fallback order:
220         //   1) externally-loaded font (provided by the embedder)
221         //   2) system font (family/style)
222         //   3) system default
223 
224         finfo->fTypeface = fResourceProvider->loadTypeface(name.c_str(), finfo->fPath.c_str());
225 
226         // legacy API fallback
227         // TODO: remove after client migration
228         if (!finfo->fTypeface) {
229             finfo->fTypeface = fmgr->makeFromData(
230                     fResourceProvider->loadFont(name.c_str(), finfo->fPath.c_str()));
231         }
232 
233         if (!finfo->fTypeface) {
234             finfo->fTypeface.reset(fmgr->matchFamilyStyle(finfo->fFamily.c_str(),
235                                                           FontStyle(this, finfo->fStyle.c_str())));
236 
237             if (!finfo->fTypeface) {
238                 this->log(Logger::Level::kError, nullptr, "Could not create typeface for %s|%s.",
239                           finfo->fFamily.c_str(), finfo->fStyle.c_str());
240                 // Last resort.
241                 finfo->fTypeface = fmgr->legacyMakeTypeface(nullptr,
242                                                             FontStyle(this, finfo->fStyle.c_str()));
243 
244                 has_unresolved |= !finfo->fTypeface;
245             }
246         }
247     });
248 
249     return !has_unresolved;
250 }
251 
resolveEmbeddedTypefaces(const skjson::ArrayValue & jchars)252 bool AnimationBuilder::resolveEmbeddedTypefaces(const skjson::ArrayValue& jchars) {
253     // Optional array of glyphs, to be associated with one of the declared fonts. E.g.
254     // "chars": [
255     //     {
256     //         "fFamily": "Roboto",       // part of the font key
257     //         "style": "Regular",        // part of the font key
258     //         ...                        // glyph data
259     //    }
260     // ]
261     FontInfo* current_font = nullptr;
262 
263     for (const skjson::ObjectValue* jchar : jchars) {
264         if (!jchar) {
265             continue;
266         }
267 
268         const skjson::StringValue* jfamily = (*jchar)["fFamily"];
269         const skjson::StringValue* jstyle  = (*jchar)["style"]; // "style", not "fStyle"...
270 
271         if (!jfamily || !jstyle) {
272             this->log(Logger::Level::kError, jchar, "Invalid glyph.");
273             continue;
274         }
275         const auto* family = jfamily->begin();
276         const auto* style  = jstyle->begin();
277 
278         // Locate (and cache) the font info. Unlike text nodes, glyphs reference the font by
279         // (family, style) -- not by name :(  For now this performs a linear search over *all*
280         // fonts: generally there are few of them, and glyph definitions are font-clustered.
281         // If problematic, we can refactor as a two-level hashmap.
282         if (!current_font || !current_font->matches(family, style)) {
283             current_font = nullptr;
284             fFonts.foreach([&](const SkString& name, FontInfo* finfo) {
285                 if (finfo->matches(family, style)) {
286                     current_font = finfo;
287                     // TODO: would be nice to break early here...
288                 }
289             });
290             if (!current_font) {
291                 this->log(Logger::Level::kError, nullptr,
292                           "Font not found (%s, %s).", family, style);
293                 continue;
294             }
295         }
296 
297         if (!current_font->fCustomFontBuilder.parseGlyph(this, *jchar)) {
298             this->log(Logger::Level::kError, jchar, "Invalid glyph.");
299         }
300     }
301 
302     // Final pass to commit custom typefaces.
303     auto has_unresolved = false;
304     std::vector<std::unique_ptr<CustomFont>> custom_fonts;
305     fFonts.foreach([&has_unresolved, &custom_fonts](const SkString&, FontInfo* finfo) {
306         if (finfo->fTypeface) {
307             return; // already resolved
308         }
309 
310         auto font = finfo->fCustomFontBuilder.detach();
311 
312         finfo->fTypeface = font->typeface();
313 
314         if (font->glyphCompCount() > 0) {
315             custom_fonts.push_back(std::move(font));
316         }
317 
318         has_unresolved |= !finfo->fTypeface;
319     });
320 
321     // Stash custom font data for later use.
322     if (!custom_fonts.empty()) {
323         custom_fonts.shrink_to_fit();
324         fCustomGlyphMapper = sk_make_sp<CustomFont::GlyphCompMapper>(std::move(custom_fonts));
325     }
326 
327     return !has_unresolved;
328 }
329 
attachTextLayer(const skjson::ObjectValue & jlayer,LayerInfo *) const330 sk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectValue& jlayer,
331                                                           LayerInfo*) const {
332     return this->attachDiscardableAdapter<TextAdapter>(jlayer,
333                                                        this,
334                                                        fLazyFontMgr.getMaybeNull(),
335                                                        fCustomGlyphMapper,
336                                                        fLogger);
337 }
338 
findFont(const SkString & font_name) const339 const AnimationBuilder::FontInfo* AnimationBuilder::findFont(const SkString& font_name) const {
340     return fFonts.find(font_name);
341 }
342 
343 } // namespace internal
344 } // namespace skottie
345