/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkottiePriv.h" #include "SkData.h" #include "SkFontMgr.h" #include "SkMakeUnique.h" #include "SkottieAdapter.h" #include "SkottieJson.h" #include "SkottieValue.h" #include "SkSGColor.h" #include "SkSGDraw.h" #include "SkSGGroup.h" #include "SkSGText.h" #include "SkTypes.h" #include namespace skottie { namespace internal { namespace { SkFontStyle FontStyle(const AnimationBuilder* abuilder, const char* style) { static constexpr struct { const char* fName; const SkFontStyle::Weight fWeight; } gWeightMap[] = { { "ExtraLight", SkFontStyle::kExtraLight_Weight }, { "Light" , SkFontStyle::kLight_Weight }, { "Regular" , SkFontStyle::kNormal_Weight }, { "Medium" , SkFontStyle::kMedium_Weight }, { "SemiBold" , SkFontStyle::kSemiBold_Weight }, { "Bold" , SkFontStyle::kBold_Weight }, { "ExtraBold" , SkFontStyle::kExtraBold_Weight }, }; SkFontStyle::Weight weight = SkFontStyle::kNormal_Weight; for (const auto& w : gWeightMap) { const auto name_len = strlen(w.fName); if (!strncmp(style, w.fName, name_len)) { weight = w.fWeight; style += name_len; break; } } static constexpr struct { const char* fName; const SkFontStyle::Slant fSlant; } gSlantMap[] = { { "Italic" , SkFontStyle::kItalic_Slant }, { "Oblique", SkFontStyle::kOblique_Slant }, }; SkFontStyle::Slant slant = SkFontStyle::kUpright_Slant; if (*style != '\0') { for (const auto& s : gSlantMap) { if (!strcmp(style, s.fName)) { slant = s.fSlant; style += strlen(s.fName); break; } } } if (*style != '\0') { abuilder->log(Logger::Level::kWarning, nullptr, "Unknown font style: %s.", style); } return SkFontStyle(weight, SkFontStyle::kNormal_Width, slant); } } // namespace bool AnimationBuilder::FontInfo::matches(const char family[], const char style[]) const { return 0 == strcmp(fFamily.c_str(), family) && 0 == strcmp(fStyle.c_str(), style); } void AnimationBuilder::parseFonts(const skjson::ObjectValue* jfonts, const skjson::ArrayValue* jchars) { // Optional array of font entries, referenced (by name) from text layer document nodes. E.g. // "fonts": { // "list": [ // { // "ascent": 75, // "fClass": "", // "fFamily": "Roboto", // "fName": "Roboto-Regular", // "fPath": "https://fonts.googleapis.com/css?family=Roboto", // "fPath": "", // "fStyle": "Regular", // "fWeight": "", // "origin": 1 // } // ] // }, if (jfonts) { if (const skjson::ArrayValue* jlist = (*jfonts)["list"]) { for (const skjson::ObjectValue* jfont : *jlist) { if (!jfont) { continue; } const skjson::StringValue* jname = (*jfont)["fName"]; const skjson::StringValue* jfamily = (*jfont)["fFamily"]; const skjson::StringValue* jstyle = (*jfont)["fStyle"]; const skjson::StringValue* jpath = (*jfont)["fPath"]; if (!jname || !jname->size() || !jfamily || !jfamily->size() || !jstyle || !jstyle->size()) { this->log(Logger::Level::kError, jfont, "Invalid font."); continue; } const auto& fmgr = fLazyFontMgr.get(); // Typeface fallback order: // 1) externally-loaded font (provided by the embedder) // 2) system font (family/style) // 3) system default sk_sp tf = fmgr->makeFromData(fResourceProvider->loadFont(jname->begin(), jpath ? jpath->begin() : nullptr)); if (!tf) { tf.reset(fmgr->matchFamilyStyle(jfamily->begin(), FontStyle(this, jstyle->begin()))); } if (!tf) { this->log(Logger::Level::kError, nullptr, "Could not create typeface for %s|%s.", jfamily->begin(), jstyle->begin()); // Last resort. tf = fmgr->legacyMakeTypeface(nullptr, FontStyle(this, jstyle->begin())); if (!tf) { continue; } } fFonts.set(SkString(jname->begin(), jname->size()), { SkString(jfamily->begin(), jfamily->size()), SkString(jstyle->begin(), jstyle->size()), ParseDefault((*jfont)["ascent"] , 0.0f), std::move(tf) }); } } } // Optional array of glyphs, to be associated with one of the declared fonts. E.g. // "chars": [ // { // "ch": "t", // "data": { // "shapes": [...] // }, // "fFamily": "Roboto", // "size": 50, // "style": "Regular", // "w": 32.67 // } // ] if (jchars) { FontInfo* current_font = nullptr; for (const skjson::ObjectValue* jchar : *jchars) { if (!jchar) { continue; } const skjson::StringValue* jch = (*jchar)["ch"]; if (!jch) { continue; } const skjson::StringValue* jfamily = (*jchar)["fFamily"]; const skjson::StringValue* jstyle = (*jchar)["style"]; // "style", not "fStyle"... const auto* ch_ptr = jch->begin(); const auto ch_len = jch->size(); if (!jfamily || !jstyle || (SkUTF::CountUTF8(ch_ptr, ch_len) != 1)) { this->log(Logger::Level::kError, jchar, "Invalid glyph."); continue; } const auto uni = SkUTF::NextUTF8(&ch_ptr, ch_ptr + ch_len); SkASSERT(uni != -1); const auto* family = jfamily->begin(); const auto* style = jstyle->begin(); // Locate (and cache) the font info. Unlike text nodes, glyphs reference the font by // (family, style) -- not by name :( For now this performs a linear search over *all* // fonts: generally there are few of them, and glyph definitions are font-clustered. // If problematic, we can refactor as a two-level hashmap. if (!current_font || !current_font->matches(family, style)) { current_font = nullptr; fFonts.foreach([&](const SkString& name, FontInfo* finfo) { if (finfo->matches(family, style)) { current_font = finfo; // TODO: would be nice to break early here... } }); if (!current_font) { this->log(Logger::Level::kError, nullptr, "Font not found for codepoint (%d, %s, %s).", uni, family, style); continue; } } // TODO: parse glyphs } } } sk_sp AnimationBuilder::findFont(const SkString& font_name) const { if (const auto* font = fFonts.find(font_name)) { return font->fTypeface; } this->log(Logger::Level::kError, nullptr, "Unknown font: \"%s\".", font_name.c_str()); return nullptr; } sk_sp AnimationBuilder::attachTextLayer(const skjson::ObjectValue& layer, const LayerInfo&, AnimatorScope* ascope) const { // General text node format: // "t": { // "a": [], // animators (TODO) // "d": { // "k": [ // { // "s": { // "f": "Roboto-Regular", // "fc": [ // 0.42, // 0.15, // 0.15 // ], // "j": 1, // "lh": 60, // "ls": 0, // "s": 50, // "t": "text align right", // "tr": 0 // }, // "t": 0 // } // ] // }, // "m": {}, // "more options" (TODO) // "p": {} // "path options" (TODO) // }, const skjson::ObjectValue* jt = layer["t"]; if (!jt) { this->log(Logger::Level::kError, &layer, "Missing text layer \"t\" property."); return nullptr; } const skjson::ArrayValue* animated_props = (*jt)["a"]; if (animated_props && animated_props->size() > 0) { this->log(Logger::Level::kWarning, nullptr, "Unsupported animated text properties."); } const skjson::ObjectValue* jd = (*jt)["d"]; if (!jd) { return nullptr; } auto text_root = sksg::Group::Make(); auto adapter = sk_make_sp(text_root); this->bindProperty(*jd, ascope, [adapter] (const TextValue& txt) { adapter->setText(txt); }); return std::move(text_root); } } // namespace internal } // namespace skottie