Home | History | Annotate | Download | only in src
      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