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