blob: 1f22657895fee61342e4613a352b9c4450037335 [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
10#include "SkFontMgr.h"
11#include "SkMakeUnique.h"
Florin Malita9402c7d2018-08-26 22:06:55 -040012#include "SkottieAdapter.h"
Florin Malitaf9c50632018-08-17 12:29:45 -040013#include "SkottieJson.h"
14#include "SkottieValue.h"
15#include "SkSGColor.h"
16#include "SkSGDraw.h"
17#include "SkSGGroup.h"
18#include "SkSGText.h"
19#include "SkTypes.h"
20
21#include <string.h>
22
23namespace skottie {
24namespace internal {
25
26namespace {
27
Florin Malitaf9c50632018-08-17 12:29:45 -040028SkFontStyle FontStyle(const char* style) {
29 static constexpr struct {
30 const char* fName;
31 const SkFontStyle::Weight fWeight;
32 } gWeightMap[] = {
33 { "ExtraLight", SkFontStyle::kExtraLight_Weight },
34 { "Light" , SkFontStyle::kLight_Weight },
35 { "Regular" , SkFontStyle::kNormal_Weight },
36 { "Medium" , SkFontStyle::kMedium_Weight },
37 { "SemiBold" , SkFontStyle::kSemiBold_Weight },
38 { "Bold" , SkFontStyle::kBold_Weight },
39 { "ExtraBold" , SkFontStyle::kExtraBold_Weight },
40 };
41
42 SkFontStyle::Weight weight = SkFontStyle::kNormal_Weight;
43 for (const auto& w : gWeightMap) {
44 const auto name_len = strlen(w.fName);
45 if (!strncmp(style, w.fName, name_len)) {
46 weight = w.fWeight;
47 style += name_len;
48 break;
49 }
50 }
51
52 static constexpr struct {
53 const char* fName;
54 const SkFontStyle::Slant fSlant;
55 } gSlantMap[] = {
56 { "Italic" , SkFontStyle::kItalic_Slant },
57 { "Oblique", SkFontStyle::kOblique_Slant },
58 };
59
60 SkFontStyle::Slant slant = SkFontStyle::kUpright_Slant;
61 if (*style != '\0') {
62 for (const auto& s : gSlantMap) {
63 if (!strcmp(style, s.fName)) {
64 slant = s.fSlant;
65 style += strlen(s.fName);
66 break;
67 }
68 }
69 }
70
71 if (*style != '\0') {
72 LOG("?? Unknown font style: %s\n", style);
73 }
74
75 return SkFontStyle(weight, SkFontStyle::kNormal_Width, slant);
76}
77
78} // namespace
79
Florin Malitafa0441b2018-08-21 13:17:27 -040080bool AnimationBuilder::FontInfo::matches(const char family[], const char style[]) const {
Florin Malitaf9c50632018-08-17 12:29:45 -040081 return 0 == strcmp(fFamily.c_str(), family)
82 && 0 == strcmp(fStyle.c_str(), style);
83}
84
Florin Malitafa0441b2018-08-21 13:17:27 -040085void AnimationBuilder::parseFonts(const skjson::ObjectValue* jfonts,
86 const skjson::ArrayValue* jchars) {
Florin Malitaf9c50632018-08-17 12:29:45 -040087 // Optional array of font entries, referenced (by name) from text layer document nodes. E.g.
88 // "fonts": {
89 // "list": [
90 // {
91 // "ascent": 75,
92 // "fClass": "",
93 // "fFamily": "Roboto",
94 // "fName": "Roboto-Regular",
95 // "fPath": "",
96 // "fStyle": "Regular",
97 // "fWeight": "",
98 // "origin": 1
99 // }
100 // ]
101 // },
102 if (jfonts) {
103 if (const skjson::ArrayValue* jlist = (*jfonts)["list"]) {
104 for (const skjson::ObjectValue* jfont : *jlist) {
105 if (!jfont) {
106 continue;
107 }
108
109 const skjson::StringValue* jname = (*jfont)["fName"];
110 const skjson::StringValue* jfamily = (*jfont)["fFamily"];
111 const skjson::StringValue* jstyle = (*jfont)["fStyle"];
112
113 if (!jname || !jname->size() ||
114 !jfamily || !jfamily->size() ||
115 !jstyle || !jstyle->size()) {
116 LogJSON(*jfont, "!! Ignoring invalid font");
117 continue;
118 }
119
Florin Malitafa0441b2018-08-21 13:17:27 -0400120 sk_sp<SkTypeface> tf(fFontMgr->matchFamilyStyle(jfamily->begin(),
121 FontStyle(jstyle->begin())));
Florin Malitaf9c50632018-08-17 12:29:45 -0400122 if (!tf) {
123 LOG("!! Could not create typeface for %s|%s\n",
124 jfamily->begin(), jstyle->begin());
125 // Last resort.
Florin Malitafa0441b2018-08-21 13:17:27 -0400126 tf.reset(fFontMgr->matchFamilyStyle("Arial", SkFontStyle::Normal()));
Florin Malitaf9c50632018-08-17 12:29:45 -0400127 if (!tf) {
128 continue;
129 }
130 }
131
Florin Malitafa0441b2018-08-21 13:17:27 -0400132 fFonts.set(SkString(jname->begin(), jname->size()),
Florin Malitaf9c50632018-08-17 12:29:45 -0400133 {
134 SkString(jfamily->begin(), jfamily->size()),
135 SkString(jstyle->begin(), jstyle->size()),
136 ParseDefault((*jfont)["ascent"] , 0.0f),
137 std::move(tf)
138 });
139 }
140 }
141 }
142
143 // Optional array of glyphs, to be associated with one of the declared fonts. E.g.
144 // "chars": [
145 // {
146 // "ch": "t",
147 // "data": {
148 // "shapes": [...]
149 // },
150 // "fFamily": "Roboto",
151 // "size": 50,
152 // "style": "Regular",
153 // "w": 32.67
154 // }
155 // ]
156 if (jchars) {
157 FontInfo* current_font = nullptr;
158
159 for (const skjson::ObjectValue* jchar : *jchars) {
160 if (!jchar) {
161 continue;
162 }
163
164 const skjson::StringValue* jch = (*jchar)["ch"];
165 if (!jch) {
166 continue;
167 }
168
169 const skjson::StringValue* jfamily = (*jchar)["fFamily"];
170 const skjson::StringValue* jstyle = (*jchar)["style"]; // "style", not "fStyle"...
171
172 const auto* ch_ptr = jch->begin();
173 const auto ch_len = jch->size();
174
175 if (!jfamily || !jstyle || (SkUTF::CountUTF8(ch_ptr, ch_len) != 1)) {
176 LogJSON(*jchar, "!! Invalid glyph");
177 continue;
178 }
179
180 const auto uni = SkUTF::NextUTF8(&ch_ptr, ch_ptr + ch_len);
181 SkASSERT(uni != -1);
182
183 const auto* family = jfamily->begin();
184 const auto* style = jstyle->begin();
185
186 // Locate (and cache) the font info. Unlike text nodes, glyphs reference the font by
187 // (family, style) -- not by name :( For now this performs a linear search over *all*
188 // fonts: generally there are few of them, and glyph definitions are font-clustered.
189 // If problematic, we can refactor as a two-level hashmap.
190 if (!current_font || !current_font->matches(family, style)) {
191 current_font = nullptr;
Florin Malitafa0441b2018-08-21 13:17:27 -0400192 fFonts.foreach([&](const SkString& name, FontInfo* finfo) {
Florin Malitaf9c50632018-08-17 12:29:45 -0400193 if (finfo->matches(family, style)) {
194 current_font = finfo;
195 // TODO: would be nice to break early here...
196 }
197 });
198 if (!current_font) {
199 LOG("!! Font not found for codepoint (%d, %s, %s)\n", uni, family, style);
200 continue;
201 }
202 }
203
Florin Malitafa0441b2018-08-21 13:17:27 -0400204 // TODO: parse glyphs
Florin Malitaf9c50632018-08-17 12:29:45 -0400205 }
206 }
Florin Malitaf9c50632018-08-17 12:29:45 -0400207}
208
Florin Malita9402c7d2018-08-26 22:06:55 -0400209sk_sp<SkTypeface> AnimationBuilder::findFont(const SkString& font_name) const {
210 if (const auto* font = fFonts.find(font_name)) {
211 return font->fTypeface;
212 }
213
214 LOG("!! Unknown font: \"%s\"\n", font_name.c_str());
215 return nullptr;
216}
217
Florin Malitafa0441b2018-08-21 13:17:27 -0400218sk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectValue& layer,
Florin Malita471a9462018-08-25 20:22:34 -0400219 AnimatorScope* ascope) const {
Florin Malitaf9c50632018-08-17 12:29:45 -0400220 // General text node format:
221 // "t": {
222 // "a": [], // animators (TODO)
223 // "d": {
224 // "k": [
225 // {
226 // "s": {
227 // "f": "Roboto-Regular",
228 // "fc": [
229 // 0.42,
230 // 0.15,
231 // 0.15
232 // ],
233 // "j": 1,
234 // "lh": 60,
235 // "ls": 0,
236 // "s": 50,
237 // "t": "text align right",
238 // "tr": 0
239 // },
240 // "t": 0
241 // }
242 // ]
243 // },
244 // "m": {}, // "more options" (TODO)
245 // "p": {} // "path options" (TODO)
246 // },
247 const skjson::ObjectValue* jt = layer["t"];
248 if (!jt) {
249 LogJSON(layer, "!! Missing text layer \"t\" property");
250 return nullptr;
251 }
252
253 const skjson::ArrayValue* animated_props = (*jt)["a"];
254 if (animated_props && animated_props->size() > 0) {
255 LOG("?? Unsupported animated text properties.\n");
256 }
257
Florin Malitaf9c50632018-08-17 12:29:45 -0400258 const skjson::ObjectValue* jd = (*jt)["d"];
Florin Malita9402c7d2018-08-26 22:06:55 -0400259 if (!jd) {
Florin Malitaf9c50632018-08-17 12:29:45 -0400260 return nullptr;
261 }
262
Florin Malita9402c7d2018-08-26 22:06:55 -0400263 auto text_root = sksg::Group::Make();
264 auto adapter = sk_make_sp<TextAdapter>(text_root);
Florin Malitaf9c50632018-08-17 12:29:45 -0400265
Florin Malita9402c7d2018-08-26 22:06:55 -0400266 this->bindProperty<TextValue>(*jd, ascope, [adapter] (const TextValue& txt) {
267 adapter->setText(txt);
268 });
Florin Malitaf9c50632018-08-17 12:29:45 -0400269
Florin Malita9402c7d2018-08-26 22:06:55 -0400270 return std::move(text_root);
Florin Malitaf9c50632018-08-17 12:29:45 -0400271}
272
273} // namespace internal
274} // namespace skottie