blob: 68929c415f47f96cb32fc23b24517eed0744d4fa [file] [log] [blame]
Florin Malitaf9c50632018-08-17 12:29:45 -04001/*
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
Florin Malita40543512018-09-19 21:43:29 -040010#include "SkData.h"
Florin Malitaf9c50632018-08-17 12:29:45 -040011#include "SkFontMgr.h"
12#include "SkMakeUnique.h"
Florin Malita9402c7d2018-08-26 22:06:55 -040013#include "SkottieAdapter.h"
Florin Malitaf9c50632018-08-17 12:29:45 -040014#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
24namespace skottie {
25namespace internal {
26
27namespace {
28
Florin Malita57b9d402018-10-02 12:48:00 -040029SkFontStyle FontStyle(const AnimationBuilder* abuilder, const char* style) {
Florin Malitaf9c50632018-08-17 12:29:45 -040030 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') {
Florin Malita57b9d402018-10-02 12:48:00 -040073 abuilder->log(Logger::Level::kWarning, nullptr, "Unknown font style: %s.", style);
Florin Malitaf9c50632018-08-17 12:29:45 -040074 }
75
76 return SkFontStyle(weight, SkFontStyle::kNormal_Width, slant);
77}
78
79} // namespace
80
Florin Malitafa0441b2018-08-21 13:17:27 -040081bool AnimationBuilder::FontInfo::matches(const char family[], const char style[]) const {
Florin Malitaf9c50632018-08-17 12:29:45 -040082 return 0 == strcmp(fFamily.c_str(), family)
83 && 0 == strcmp(fStyle.c_str(), style);
84}
85
Florin Malitafa0441b2018-08-21 13:17:27 -040086void AnimationBuilder::parseFonts(const skjson::ObjectValue* jfonts,
87 const skjson::ArrayValue* jchars) {
Florin Malitaf9c50632018-08-17 12:29:45 -040088 // 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",
Florin Malita40543512018-09-19 21:43:29 -040096 // "fPath": "https://fonts.googleapis.com/css?family=Roboto",
Florin Malitaf9c50632018-08-17 12:29:45 -040097 // "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"];
Florin Malita40543512018-09-19 21:43:29 -0400114 const skjson::StringValue* jpath = (*jfont)["fPath"];
Florin Malitaf9c50632018-08-17 12:29:45 -0400115
116 if (!jname || !jname->size() ||
117 !jfamily || !jfamily->size() ||
118 !jstyle || !jstyle->size()) {
Florin Malita57b9d402018-10-02 12:48:00 -0400119 this->log(Logger::Level::kError, jfont, "Invalid font.");
Florin Malitaf9c50632018-08-17 12:29:45 -0400120 continue;
121 }
122
Florin Malitabe5e8652018-08-31 12:54:18 -0400123 const auto& fmgr = fLazyFontMgr.get();
Florin Malita40543512018-09-19 21:43:29 -0400124
125 // Typeface fallback order:
Florin Malita97b150e2018-09-27 10:59:52 -0400126 // 1) externally-loaded font (provided by the embedder)
Florin Malita40543512018-09-19 21:43:29 -0400127 // 2) system font (family/style)
128 // 3) system default
129
Florin Malita97b150e2018-09-27 10:59:52 -0400130 sk_sp<SkTypeface> tf =
131 fmgr->makeFromData(fResourceProvider->loadFont(jname->begin(),
132 jpath ? jpath->begin()
133 : nullptr));
Florin Malita40543512018-09-19 21:43:29 -0400134
135 if (!tf) {
Florin Malita57b9d402018-10-02 12:48:00 -0400136 tf.reset(fmgr->matchFamilyStyle(jfamily->begin(),
137 FontStyle(this, jstyle->begin())));
Florin Malita40543512018-09-19 21:43:29 -0400138 }
139
Florin Malitaf9c50632018-08-17 12:29:45 -0400140 if (!tf) {
Florin Malita57b9d402018-10-02 12:48:00 -0400141 this->log(Logger::Level::kError, nullptr,
142 "Could not create typeface for %s|%s.",
143 jfamily->begin(), jstyle->begin());
Florin Malitaf9c50632018-08-17 12:29:45 -0400144 // Last resort.
Florin Malita57b9d402018-10-02 12:48:00 -0400145 tf = fmgr->legacyMakeTypeface(nullptr, FontStyle(this, jstyle->begin()));
Florin Malitaf9c50632018-08-17 12:29:45 -0400146 if (!tf) {
147 continue;
148 }
149 }
150
Florin Malitafa0441b2018-08-21 13:17:27 -0400151 fFonts.set(SkString(jname->begin(), jname->size()),
Florin Malitaf9c50632018-08-17 12:29:45 -0400152 {
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)) {
Florin Malita57b9d402018-10-02 12:48:00 -0400195 this->log(Logger::Level::kError, jchar, "Invalid glyph.");
Florin Malitaf9c50632018-08-17 12:29:45 -0400196 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;
Florin Malitafa0441b2018-08-21 13:17:27 -0400211 fFonts.foreach([&](const SkString& name, FontInfo* finfo) {
Florin Malitaf9c50632018-08-17 12:29:45 -0400212 if (finfo->matches(family, style)) {
213 current_font = finfo;
214 // TODO: would be nice to break early here...
215 }
216 });
217 if (!current_font) {
Florin Malita57b9d402018-10-02 12:48:00 -0400218 this->log(Logger::Level::kError, nullptr,
219 "Font not found for codepoint (%d, %s, %s).", uni, family, style);
Florin Malitaf9c50632018-08-17 12:29:45 -0400220 continue;
221 }
222 }
223
Florin Malitafa0441b2018-08-21 13:17:27 -0400224 // TODO: parse glyphs
Florin Malitaf9c50632018-08-17 12:29:45 -0400225 }
226 }
Florin Malitaf9c50632018-08-17 12:29:45 -0400227}
228
Florin Malita9402c7d2018-08-26 22:06:55 -0400229sk_sp<SkTypeface> AnimationBuilder::findFont(const SkString& font_name) const {
230 if (const auto* font = fFonts.find(font_name)) {
231 return font->fTypeface;
232 }
233
Florin Malita57b9d402018-10-02 12:48:00 -0400234 this->log(Logger::Level::kError, nullptr, "Unknown font: \"%s\".", font_name.c_str());
Florin Malita9402c7d2018-08-26 22:06:55 -0400235 return nullptr;
236}
237
Florin Malitafa0441b2018-08-21 13:17:27 -0400238sk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectValue& layer,
Florin Malita62c6bd92018-10-03 14:39:56 -0400239 const LayerInfo&,
Florin Malita471a9462018-08-25 20:22:34 -0400240 AnimatorScope* ascope) const {
Florin Malitaf9c50632018-08-17 12:29:45 -0400241 // 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) {
Florin Malita57b9d402018-10-02 12:48:00 -0400270 this->log(Logger::Level::kError, &layer, "Missing text layer \"t\" property.");
Florin Malitaf9c50632018-08-17 12:29:45 -0400271 return nullptr;
272 }
273
274 const skjson::ArrayValue* animated_props = (*jt)["a"];
275 if (animated_props && animated_props->size() > 0) {
Florin Malita57b9d402018-10-02 12:48:00 -0400276 this->log(Logger::Level::kWarning, nullptr, "Unsupported animated text properties.");
Florin Malitaf9c50632018-08-17 12:29:45 -0400277 }
278
Florin Malitaf9c50632018-08-17 12:29:45 -0400279 const skjson::ObjectValue* jd = (*jt)["d"];
Florin Malita9402c7d2018-08-26 22:06:55 -0400280 if (!jd) {
Florin Malitaf9c50632018-08-17 12:29:45 -0400281 return nullptr;
282 }
283
Florin Malita9402c7d2018-08-26 22:06:55 -0400284 auto text_root = sksg::Group::Make();
285 auto adapter = sk_make_sp<TextAdapter>(text_root);
Florin Malitaf9c50632018-08-17 12:29:45 -0400286
Florin Malita9402c7d2018-08-26 22:06:55 -0400287 this->bindProperty<TextValue>(*jd, ascope, [adapter] (const TextValue& txt) {
288 adapter->setText(txt);
289 });
Florin Malitaf9c50632018-08-17 12:29:45 -0400290
Florin Malita9402c7d2018-08-26 22:06:55 -0400291 return std::move(text_root);
Florin Malitaf9c50632018-08-17 12:29:45 -0400292}
293
294} // namespace internal
295} // namespace skottie