Flutter Engine
The Flutter Engine
TextLayer.cpp
Go to the documentation of this file.
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
20#include "modules/sksg/include/SkSGGroup.h" // IWYU pragma: keep
22#include "src/base/SkTSearch.h"
23#include "src/core/SkTHash.h"
24#include "src/utils/SkJSON.h"
25
26#include <string.h>
27#include <memory>
28#include <tuple>
29#include <utility>
30#include <vector>
31
32namespace skottie {
33namespace internal {
34
35namespace {
36
37template <typename T, typename TMap>
38const char* parse_map(const TMap& map, const char* str, T* result) {
39 // ignore leading whitespace
40 while (*str == ' ') ++str;
41
42 const char* next_tok = strchr(str, ' ');
43
44 if (const auto len = next_tok ? (next_tok - str) : strlen(str)) {
45 for (const auto& e : map) {
46 const char* key = std::get<0>(e);
47 if (!strncmp(str, key, len) && key[len] == '\0') {
49 return str + len;
50 }
51 }
52 }
53
54 return str;
55}
56
57SkFontStyle FontStyle(const AnimationBuilder* abuilder, const char* style) {
58 static constexpr std::tuple<const char*, SkFontStyle::Weight> gWeightMap[] = {
59 { "regular" , SkFontStyle::kNormal_Weight },
60 { "medium" , SkFontStyle::kMedium_Weight },
61 { "bold" , SkFontStyle::kBold_Weight },
62 { "light" , SkFontStyle::kLight_Weight },
63 { "black" , SkFontStyle::kBlack_Weight },
64 { "thin" , SkFontStyle::kThin_Weight },
66 { "extrabold" , SkFontStyle::kExtraBold_Weight },
67 { "extralight", SkFontStyle::kExtraLight_Weight },
68 { "extrablack", SkFontStyle::kExtraBlack_Weight },
69 { "semibold" , SkFontStyle::kSemiBold_Weight },
70 { "hairline" , SkFontStyle::kThin_Weight },
71 { "normal" , SkFontStyle::kNormal_Weight },
72 { "plain" , SkFontStyle::kNormal_Weight },
73 { "standard" , SkFontStyle::kNormal_Weight },
74 { "roman" , SkFontStyle::kNormal_Weight },
75 { "heavy" , SkFontStyle::kBlack_Weight },
77 { "demibold" , SkFontStyle::kSemiBold_Weight },
79 { "ultrabold" , SkFontStyle::kExtraBold_Weight },
80 { "ultrablack", SkFontStyle::kExtraBlack_Weight },
81 { "ultraheavy", SkFontStyle::kExtraBlack_Weight },
82 { "ultralight", SkFontStyle::kExtraLight_Weight },
83 };
84 static constexpr std::tuple<const char*, SkFontStyle::Slant> gSlantMap[] = {
85 { "italic" , SkFontStyle::kItalic_Slant },
86 { "oblique", SkFontStyle::kOblique_Slant },
87 };
88
89 auto weight = SkFontStyle::kNormal_Weight;
90 auto slant = SkFontStyle::kUpright_Slant;
91
92 // style is case insensitive.
93 SkAutoAsciiToLC lc_style(style);
94 style = lc_style.lc();
95 style = parse_map(gWeightMap, style, &weight);
96 style = parse_map(gSlantMap , style, &slant );
97
98 // ignore trailing whitespace
99 while (*style == ' ') ++style;
100
101 if (*style) {
102 abuilder->log(Logger::Level::kWarning, nullptr, "Unknown font style: %s.", style);
103 }
104
105 return SkFontStyle(weight, SkFontStyle::kNormal_Width, slant);
106}
107
108} // namespace
109
110bool AnimationBuilder::FontInfo::matches(const char family[], const char style[]) const {
111 return 0 == strcmp(fFamily.c_str(), family)
112 && 0 == strcmp(fStyle.c_str(), style);
113}
114
116 const skjson::ArrayValue* jchars) {
117 // Optional array of font entries, referenced (by name) from text layer document nodes. E.g.
118 // "fonts": {
119 // "list": [
120 // {
121 // "ascent": 75,
122 // "fClass": "",
123 // "fFamily": "Roboto",
124 // "fName": "Roboto-Regular",
125 // "fPath": "https://fonts.googleapis.com/css?family=Roboto",
126 // "fPath": "",
127 // "fStyle": "Regular",
128 // "fWeight": "",
129 // "origin": 1
130 // }
131 // ]
132 // },
133 const skjson::ArrayValue* jlist = jfonts
134 ? static_cast<const skjson::ArrayValue*>((*jfonts)["list"])
135 : nullptr;
136 if (!jlist) {
137 return;
138 }
139
140 // First pass: collect font info.
141 for (const skjson::ObjectValue* jfont : *jlist) {
142 if (!jfont) {
143 continue;
144 }
145
146 const skjson::StringValue* jname = (*jfont)["fName"];
147 const skjson::StringValue* jfamily = (*jfont)["fFamily"];
148 const skjson::StringValue* jstyle = (*jfont)["fStyle"];
149 const skjson::StringValue* jpath = (*jfont)["fPath"];
150
151 if (!jname || !jname->size() ||
152 !jfamily || !jfamily->size() ||
153 !jstyle) {
154 this->log(Logger::Level::kError, jfont, "Invalid font.");
155 continue;
156 }
157
158 fFonts.set(SkString(jname->begin(), jname->size()),
159 {
160 SkString(jfamily->begin(), jfamily->size()),
161 SkString( jstyle->begin(), jstyle->size()),
162 jpath ? SkString( jpath->begin(), jpath->size()) : SkString(),
163 ParseDefault((*jfont)["ascent"] , 0.0f),
164 nullptr, // placeholder
165 CustomFont::Builder()
166 });
167 }
168
169 const auto has_comp_glyphs = [](const skjson::ArrayValue* jchars) {
170 if (!jchars) {
171 return false;
172 }
173
174 for (const skjson::ObjectValue* jchar : *jchars) {
175 if (!jchar) {
176 continue;
177 }
178 if (ParseDefault<int>((*jchar)["t"], 0) == 1) {
179 return true;
180 }
181 }
182
183 return false;
184 };
185
186 // Historically, Skottie has been loading native fonts before embedded glyphs, unless
187 // the opposite is explicitly requested via kPreferEmbeddedFonts. That's mostly because
188 // embedded glyphs used to be just a path representation of system fonts at export time,
189 // (and thus lower quality).
190 //
191 // OTOH embedded glyph *compositions* must be prioritized, as they are presumably more
192 // expressive than the system font equivalent.
193 const auto prioritize_embedded_fonts =
194 (fFlags & Animation::Builder::kPreferEmbeddedFonts) || has_comp_glyphs(jchars);
195
196 // Optional pass.
197 if (jchars && prioritize_embedded_fonts && this->resolveEmbeddedTypefaces(*jchars)) {
198 return;
199 }
200
201 // Native typeface resolution.
202 if (this->resolveNativeTypefaces()) {
203 return;
204 }
205
206 // Embedded typeface fallback.
207 if (jchars && !prioritize_embedded_fonts) {
208 this->resolveEmbeddedTypefaces(*jchars);
209 }
210}
211
212bool AnimationBuilder::resolveNativeTypefaces() {
213 bool has_unresolved = false;
214
215 fFonts.foreach([&](const SkString& name, FontInfo* finfo) {
216 SkASSERT(finfo);
217
218 if (finfo->fTypeface) {
219 // Already resolved from glyph paths.
220 return;
221 }
222
223 // Typeface fallback order:
224 // 1) externally-loaded font (provided by the embedder)
225 // 2) system font (family/style)
226 // 3) system default
227
228 finfo->fTypeface = fResourceProvider->loadTypeface(name.c_str(), finfo->fPath.c_str());
229
230 // legacy API fallback
231 // TODO: remove after client migration
232 if (!finfo->fTypeface && fFontMgr) {
233 finfo->fTypeface = fFontMgr->makeFromData(
234 fResourceProvider->loadFont(name.c_str(), finfo->fPath.c_str()));
235 }
236
237 if (!finfo->fTypeface && fFontMgr) {
238 finfo->fTypeface = fFontMgr->matchFamilyStyle(finfo->fFamily.c_str(),
239 FontStyle(this, finfo->fStyle.c_str()));
240
241 if (!finfo->fTypeface) {
242 this->log(Logger::Level::kError, nullptr, "Could not create typeface for %s|%s.",
243 finfo->fFamily.c_str(), finfo->fStyle.c_str());
244 // Last resort.
245 finfo->fTypeface = fFontMgr->legacyMakeTypeface(nullptr,
246 FontStyle(this, finfo->fStyle.c_str()));
247
248 has_unresolved |= !finfo->fTypeface;
249 }
250 }
251 if (!finfo->fTypeface && !fFontMgr) {
252 this->log(Logger::Level::kError, nullptr,
253 "Could not load typeface for %s|%s because no SkFontMgr provided.",
254 finfo->fFamily.c_str(), finfo->fStyle.c_str());
255 }
256 });
257
258 return !has_unresolved;
259}
260
261bool AnimationBuilder::resolveEmbeddedTypefaces(const skjson::ArrayValue& jchars) {
262 // Optional array of glyphs, to be associated with one of the declared fonts. E.g.
263 // "chars": [
264 // {
265 // "fFamily": "Roboto", // part of the font key
266 // "style": "Regular", // part of the font key
267 // ... // glyph data
268 // }
269 // ]
270 FontInfo* current_font = nullptr;
271
272 for (const skjson::ObjectValue* jchar : jchars) {
273 if (!jchar) {
274 continue;
275 }
276
277 const skjson::StringValue* jfamily = (*jchar)["fFamily"];
278 const skjson::StringValue* jstyle = (*jchar)["style"]; // "style", not "fStyle"...
279
280 if (!jfamily || !jstyle) {
281 this->log(Logger::Level::kError, jchar, "Invalid glyph.");
282 continue;
283 }
284 const auto* family = jfamily->begin();
285 const auto* style = jstyle->begin();
286
287 // Locate (and cache) the font info. Unlike text nodes, glyphs reference the font by
288 // (family, style) -- not by name :( For now this performs a linear search over *all*
289 // fonts: generally there are few of them, and glyph definitions are font-clustered.
290 // If problematic, we can refactor as a two-level hashmap.
291 if (!current_font || !current_font->matches(family, style)) {
292 current_font = nullptr;
293 fFonts.foreach([&](const SkString& name, FontInfo* finfo) {
294 if (finfo->matches(family, style)) {
295 current_font = finfo;
296 // TODO: would be nice to break early here...
297 }
298 });
299 if (!current_font) {
300 this->log(Logger::Level::kError, nullptr,
301 "Font not found (%s, %s).", family, style);
302 continue;
303 }
304 }
305
306 if (!current_font->fCustomFontBuilder.parseGlyph(this, *jchar)) {
307 this->log(Logger::Level::kError, jchar, "Invalid glyph.");
308 }
309 }
310
311 // Final pass to commit custom typefaces.
312 bool has_unresolved = false;
313 std::vector<std::unique_ptr<CustomFont>> custom_fonts;
314 fFonts.foreach([&has_unresolved, &custom_fonts](const SkString&, FontInfo* finfo) {
315 if (finfo->fTypeface) {
316 return; // already resolved
317 }
318
319 auto font = finfo->fCustomFontBuilder.detach();
320
321 finfo->fTypeface = font->typeface();
322
323 if (font->glyphCompCount() > 0) {
324 custom_fonts.push_back(std::move(font));
325 }
326
327 has_unresolved |= !finfo->fTypeface;
328 });
329
330 // Stash custom font data for later use.
331 if (!custom_fonts.empty()) {
332 custom_fonts.shrink_to_fit();
333 fCustomGlyphMapper = sk_make_sp<CustomFont::GlyphCompMapper>(std::move(custom_fonts));
334 }
335
336 return !has_unresolved;
337}
338
339sk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectValue& jlayer,
340 LayerInfo*) const {
341 return this->attachDiscardableAdapter<TextAdapter>(jlayer,
342 this,
343 fFontMgr,
344 fCustomGlyphMapper,
345 fLogger,
346 fShapingFactory);
347}
348
350 return fFonts.find(font_name);
351}
352
353} // namespace internal
354} // namespace skottie
#define SkASSERT(cond)
Definition: SkAssert.h:116
sk_sp< SkTypeface > makeFromData(sk_sp< SkData >, int ttcIndex=0) const
Definition: SkFontMgr.cpp:120
sk_sp< SkTypeface > legacyMakeTypeface(const char familyName[], SkFontStyle style) const
Definition: SkFontMgr.cpp:150
sk_sp< SkTypeface > matchFamilyStyle(const char familyName[], const SkFontStyle &) const
Definition: SkFontMgr.cpp:109
@ kSemiBold_Weight
Definition: SkFontStyle.h:25
@ kExtraBlack_Weight
Definition: SkFontStyle.h:29
@ kExtraBold_Weight
Definition: SkFontStyle.h:27
@ kExtraLight_Weight
Definition: SkFontStyle.h:21
const char * c_str() const
Definition: SkString.h:133
const char * begin() const
Definition: SkJSON.h:315
size_t size() const
Definition: SkJSON.h:300
void parseFonts(const skjson::ObjectValue *jfonts, const skjson::ArrayValue *jchars)
Definition: TextLayer.cpp:115
void log(Logger::Level, const skjson::Value *, const char fmt[],...) const SK_PRINTF_LIKE(4
Definition: Skottie.cpp:71
const FontInfo * findFont(const SkString &name) const
Definition: TextLayer.cpp:349
if(end==-1)
GAsyncResult * result
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
font
Font Metadata and Metrics.
const myers::Point & get< 1 >(const myers::Segment &s)
Definition: Myers.h:81
const myers::Point & get< 0 >(const myers::Segment &s)
Definition: Myers.h:80
SI auto map(std::index_sequence< I... >, Fn &&fn, const Args &... args) -> skvx::Vec< sizeof...(I), decltype(fn(args[0]...))>
Definition: SkVx.h:680
FontStyle
Definition: font_style.h:22
#define T
Definition: precompiler.cc:65
bool matches(const char family[], const char style[]) const
Definition: TextLayer.cpp:110