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