1cb93a386Sopenharmony_ci/*
2cb93a386Sopenharmony_ci * Copyright 2018 Google Inc.
3cb93a386Sopenharmony_ci *
4cb93a386Sopenharmony_ci * Use of this source code is governed by a BSD-style license that can be
5cb93a386Sopenharmony_ci * found in the LICENSE file.
6cb93a386Sopenharmony_ci */
7cb93a386Sopenharmony_ci
8cb93a386Sopenharmony_ci#include "modules/skottie/src/SkottiePriv.h"
9cb93a386Sopenharmony_ci
10cb93a386Sopenharmony_ci#include "include/core/SkData.h"
11cb93a386Sopenharmony_ci#include "include/core/SkFontMgr.h"
12cb93a386Sopenharmony_ci#include "include/core/SkTypes.h"
13cb93a386Sopenharmony_ci#include "modules/skottie/src/SkottieJson.h"
14cb93a386Sopenharmony_ci#include "modules/skottie/src/text/TextAdapter.h"
15cb93a386Sopenharmony_ci#include "modules/skottie/src/text/TextAnimator.h"
16cb93a386Sopenharmony_ci#include "modules/skottie/src/text/TextValue.h"
17cb93a386Sopenharmony_ci#include "modules/sksg/include/SkSGDraw.h"
18cb93a386Sopenharmony_ci#include "modules/sksg/include/SkSGGroup.h"
19cb93a386Sopenharmony_ci#include "modules/sksg/include/SkSGPaint.h"
20cb93a386Sopenharmony_ci#include "modules/sksg/include/SkSGPath.h"
21cb93a386Sopenharmony_ci#include "modules/sksg/include/SkSGText.h"
22cb93a386Sopenharmony_ci#include "src/core/SkTSearch.h"
23cb93a386Sopenharmony_ci
24cb93a386Sopenharmony_ci#include <string.h>
25cb93a386Sopenharmony_ci
26cb93a386Sopenharmony_cinamespace skottie {
27cb93a386Sopenharmony_cinamespace internal {
28cb93a386Sopenharmony_ci
29cb93a386Sopenharmony_cinamespace {
30cb93a386Sopenharmony_ci
31cb93a386Sopenharmony_citemplate <typename T, typename TMap>
32cb93a386Sopenharmony_ciconst char* parse_map(const TMap& map, const char* str, T* result) {
33cb93a386Sopenharmony_ci    // ignore leading whitespace
34cb93a386Sopenharmony_ci    while (*str == ' ') ++str;
35cb93a386Sopenharmony_ci
36cb93a386Sopenharmony_ci    const char* next_tok = strchr(str, ' ');
37cb93a386Sopenharmony_ci
38cb93a386Sopenharmony_ci    if (const auto len = next_tok ? (next_tok - str) : strlen(str)) {
39cb93a386Sopenharmony_ci        for (const auto& e : map) {
40cb93a386Sopenharmony_ci            const char* key = std::get<0>(e);
41cb93a386Sopenharmony_ci            if (!strncmp(str, key, len) && key[len] == '\0') {
42cb93a386Sopenharmony_ci                *result = std::get<1>(e);
43cb93a386Sopenharmony_ci                return str + len;
44cb93a386Sopenharmony_ci            }
45cb93a386Sopenharmony_ci        }
46cb93a386Sopenharmony_ci    }
47cb93a386Sopenharmony_ci
48cb93a386Sopenharmony_ci    return str;
49cb93a386Sopenharmony_ci}
50cb93a386Sopenharmony_ci
51cb93a386Sopenharmony_ciSkFontStyle FontStyle(const AnimationBuilder* abuilder, const char* style) {
52cb93a386Sopenharmony_ci    static constexpr std::tuple<const char*, SkFontStyle::Weight> gWeightMap[] = {
53cb93a386Sopenharmony_ci        { "regular"   , SkFontStyle::kNormal_Weight     },
54cb93a386Sopenharmony_ci        { "medium"    , SkFontStyle::kMedium_Weight     },
55cb93a386Sopenharmony_ci        { "bold"      , SkFontStyle::kBold_Weight       },
56cb93a386Sopenharmony_ci        { "light"     , SkFontStyle::kLight_Weight      },
57cb93a386Sopenharmony_ci        { "black"     , SkFontStyle::kBlack_Weight      },
58cb93a386Sopenharmony_ci        { "thin"      , SkFontStyle::kThin_Weight       },
59cb93a386Sopenharmony_ci        { "extra"     , SkFontStyle::kExtraBold_Weight  },
60cb93a386Sopenharmony_ci        { "extrabold" , SkFontStyle::kExtraBold_Weight  },
61cb93a386Sopenharmony_ci        { "extralight", SkFontStyle::kExtraLight_Weight },
62cb93a386Sopenharmony_ci        { "extrablack", SkFontStyle::kExtraBlack_Weight },
63cb93a386Sopenharmony_ci        { "semibold"  , SkFontStyle::kSemiBold_Weight   },
64cb93a386Sopenharmony_ci        { "hairline"  , SkFontStyle::kThin_Weight       },
65cb93a386Sopenharmony_ci        { "normal"    , SkFontStyle::kNormal_Weight     },
66cb93a386Sopenharmony_ci        { "plain"     , SkFontStyle::kNormal_Weight     },
67cb93a386Sopenharmony_ci        { "standard"  , SkFontStyle::kNormal_Weight     },
68cb93a386Sopenharmony_ci        { "roman"     , SkFontStyle::kNormal_Weight     },
69cb93a386Sopenharmony_ci        { "heavy"     , SkFontStyle::kBlack_Weight      },
70cb93a386Sopenharmony_ci        { "demi"      , SkFontStyle::kSemiBold_Weight   },
71cb93a386Sopenharmony_ci        { "demibold"  , SkFontStyle::kSemiBold_Weight   },
72cb93a386Sopenharmony_ci        { "ultra"     , SkFontStyle::kExtraBold_Weight  },
73cb93a386Sopenharmony_ci        { "ultrabold" , SkFontStyle::kExtraBold_Weight  },
74cb93a386Sopenharmony_ci        { "ultrablack", SkFontStyle::kExtraBlack_Weight },
75cb93a386Sopenharmony_ci        { "ultraheavy", SkFontStyle::kExtraBlack_Weight },
76cb93a386Sopenharmony_ci        { "ultralight", SkFontStyle::kExtraLight_Weight },
77cb93a386Sopenharmony_ci    };
78cb93a386Sopenharmony_ci    static constexpr std::tuple<const char*, SkFontStyle::Slant> gSlantMap[] = {
79cb93a386Sopenharmony_ci        { "italic" , SkFontStyle::kItalic_Slant  },
80cb93a386Sopenharmony_ci        { "oblique", SkFontStyle::kOblique_Slant },
81cb93a386Sopenharmony_ci    };
82cb93a386Sopenharmony_ci
83cb93a386Sopenharmony_ci    auto weight = SkFontStyle::kNormal_Weight;
84cb93a386Sopenharmony_ci    auto slant  = SkFontStyle::kUpright_Slant;
85cb93a386Sopenharmony_ci
86cb93a386Sopenharmony_ci    // style is case insensitive.
87cb93a386Sopenharmony_ci    SkAutoAsciiToLC lc_style(style);
88cb93a386Sopenharmony_ci    style = lc_style.lc();
89cb93a386Sopenharmony_ci    style = parse_map(gWeightMap, style, &weight);
90cb93a386Sopenharmony_ci    style = parse_map(gSlantMap , style, &slant );
91cb93a386Sopenharmony_ci
92cb93a386Sopenharmony_ci    // ignore trailing whitespace
93cb93a386Sopenharmony_ci    while (*style == ' ') ++style;
94cb93a386Sopenharmony_ci
95cb93a386Sopenharmony_ci    if (*style) {
96cb93a386Sopenharmony_ci        abuilder->log(Logger::Level::kWarning, nullptr, "Unknown font style: %s.", style);
97cb93a386Sopenharmony_ci    }
98cb93a386Sopenharmony_ci
99cb93a386Sopenharmony_ci    return SkFontStyle(weight, SkFontStyle::kNormal_Width, slant);
100cb93a386Sopenharmony_ci}
101cb93a386Sopenharmony_ci
102cb93a386Sopenharmony_cibool parse_glyph_path(const skjson::ObjectValue* jdata,
103cb93a386Sopenharmony_ci                      const AnimationBuilder* abuilder,
104cb93a386Sopenharmony_ci                      SkPath* path) {
105cb93a386Sopenharmony_ci    // Glyph path encoding:
106cb93a386Sopenharmony_ci    //
107cb93a386Sopenharmony_ci    //   "data": {
108cb93a386Sopenharmony_ci    //       "shapes": [                         // follows the shape layer format
109cb93a386Sopenharmony_ci    //           {
110cb93a386Sopenharmony_ci    //               "ty": "gr",                 // group shape type
111cb93a386Sopenharmony_ci    //               "it": [                     // group items
112cb93a386Sopenharmony_ci    //                   {
113cb93a386Sopenharmony_ci    //                       "ty": "sh",         // actual shape
114cb93a386Sopenharmony_ci    //                       "ks": <path data>   // animatable path format, but always static
115cb93a386Sopenharmony_ci    //                   },
116cb93a386Sopenharmony_ci    //                   ...
117cb93a386Sopenharmony_ci    //               ]
118cb93a386Sopenharmony_ci    //           },
119cb93a386Sopenharmony_ci    //           ...
120cb93a386Sopenharmony_ci    //       ]
121cb93a386Sopenharmony_ci    //   }
122cb93a386Sopenharmony_ci
123cb93a386Sopenharmony_ci    if (!jdata) {
124cb93a386Sopenharmony_ci        return false;
125cb93a386Sopenharmony_ci    }
126cb93a386Sopenharmony_ci
127cb93a386Sopenharmony_ci    const skjson::ArrayValue* jshapes = (*jdata)["shapes"];
128cb93a386Sopenharmony_ci    if (!jshapes) {
129cb93a386Sopenharmony_ci        // Space/empty glyph.
130cb93a386Sopenharmony_ci        return true;
131cb93a386Sopenharmony_ci    }
132cb93a386Sopenharmony_ci
133cb93a386Sopenharmony_ci    for (const skjson::ObjectValue* jgrp : *jshapes) {
134cb93a386Sopenharmony_ci        if (!jgrp) {
135cb93a386Sopenharmony_ci            return false;
136cb93a386Sopenharmony_ci        }
137cb93a386Sopenharmony_ci
138cb93a386Sopenharmony_ci        const skjson::ArrayValue* jit = (*jgrp)["it"];
139cb93a386Sopenharmony_ci        if (!jit) {
140cb93a386Sopenharmony_ci            return false;
141cb93a386Sopenharmony_ci        }
142cb93a386Sopenharmony_ci
143cb93a386Sopenharmony_ci        for (const skjson::ObjectValue* jshape : *jit) {
144cb93a386Sopenharmony_ci            if (!jshape) {
145cb93a386Sopenharmony_ci                return false;
146cb93a386Sopenharmony_ci            }
147cb93a386Sopenharmony_ci
148cb93a386Sopenharmony_ci            // Glyph paths should never be animated.  But they are encoded as
149cb93a386Sopenharmony_ci            // animatable properties, so we use the appropriate helpers.
150cb93a386Sopenharmony_ci            AnimationBuilder::AutoScope ascope(abuilder);
151cb93a386Sopenharmony_ci            auto path_node = abuilder->attachPath((*jshape)["ks"]);
152cb93a386Sopenharmony_ci            auto animators = ascope.release();
153cb93a386Sopenharmony_ci
154cb93a386Sopenharmony_ci            if (!path_node || !animators.empty()) {
155cb93a386Sopenharmony_ci                return false;
156cb93a386Sopenharmony_ci            }
157cb93a386Sopenharmony_ci
158cb93a386Sopenharmony_ci            // Successfully parsed a static path.  Whew.
159cb93a386Sopenharmony_ci            path->addPath(path_node->getPath());
160cb93a386Sopenharmony_ci        }
161cb93a386Sopenharmony_ci    }
162cb93a386Sopenharmony_ci
163cb93a386Sopenharmony_ci    return true;
164cb93a386Sopenharmony_ci}
165cb93a386Sopenharmony_ci
166cb93a386Sopenharmony_ci} // namespace
167cb93a386Sopenharmony_ci
168cb93a386Sopenharmony_cibool AnimationBuilder::FontInfo::matches(const char family[], const char style[]) const {
169cb93a386Sopenharmony_ci    return 0 == strcmp(fFamily.c_str(), family)
170cb93a386Sopenharmony_ci        && 0 == strcmp(fStyle.c_str(), style);
171cb93a386Sopenharmony_ci}
172cb93a386Sopenharmony_ci
173cb93a386Sopenharmony_ci#ifdef SK_NO_FONTS
174cb93a386Sopenharmony_civoid AnimationBuilder::parseFonts(const skjson::ObjectValue* jfonts,
175cb93a386Sopenharmony_ci                                  const skjson::ArrayValue* jchars) {}
176cb93a386Sopenharmony_ci
177cb93a386Sopenharmony_cisk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectValue& jlayer,
178cb93a386Sopenharmony_ci                                                          LayerInfo*) const {
179cb93a386Sopenharmony_ci    return nullptr;
180cb93a386Sopenharmony_ci}
181cb93a386Sopenharmony_ci#else
182cb93a386Sopenharmony_civoid AnimationBuilder::parseFonts(const skjson::ObjectValue* jfonts,
183cb93a386Sopenharmony_ci                                  const skjson::ArrayValue* jchars) {
184cb93a386Sopenharmony_ci    // Optional array of font entries, referenced (by name) from text layer document nodes. E.g.
185cb93a386Sopenharmony_ci    // "fonts": {
186cb93a386Sopenharmony_ci    //        "list": [
187cb93a386Sopenharmony_ci    //            {
188cb93a386Sopenharmony_ci    //                "ascent": 75,
189cb93a386Sopenharmony_ci    //                "fClass": "",
190cb93a386Sopenharmony_ci    //                "fFamily": "Roboto",
191cb93a386Sopenharmony_ci    //                "fName": "Roboto-Regular",
192cb93a386Sopenharmony_ci    //                "fPath": "https://fonts.googleapis.com/css?family=Roboto",
193cb93a386Sopenharmony_ci    //                "fPath": "",
194cb93a386Sopenharmony_ci    //                "fStyle": "Regular",
195cb93a386Sopenharmony_ci    //                "fWeight": "",
196cb93a386Sopenharmony_ci    //                "origin": 1
197cb93a386Sopenharmony_ci    //            }
198cb93a386Sopenharmony_ci    //        ]
199cb93a386Sopenharmony_ci    //    },
200cb93a386Sopenharmony_ci    const skjson::ArrayValue* jlist = jfonts
201cb93a386Sopenharmony_ci            ? static_cast<const skjson::ArrayValue*>((*jfonts)["list"])
202cb93a386Sopenharmony_ci            : nullptr;
203cb93a386Sopenharmony_ci    if (!jlist) {
204cb93a386Sopenharmony_ci        return;
205cb93a386Sopenharmony_ci    }
206cb93a386Sopenharmony_ci
207cb93a386Sopenharmony_ci    // First pass: collect font info.
208cb93a386Sopenharmony_ci    for (const skjson::ObjectValue* jfont : *jlist) {
209cb93a386Sopenharmony_ci        if (!jfont) {
210cb93a386Sopenharmony_ci            continue;
211cb93a386Sopenharmony_ci        }
212cb93a386Sopenharmony_ci
213cb93a386Sopenharmony_ci        const skjson::StringValue* jname   = (*jfont)["fName"];
214cb93a386Sopenharmony_ci        const skjson::StringValue* jfamily = (*jfont)["fFamily"];
215cb93a386Sopenharmony_ci        const skjson::StringValue* jstyle  = (*jfont)["fStyle"];
216cb93a386Sopenharmony_ci        const skjson::StringValue* jpath   = (*jfont)["fPath"];
217cb93a386Sopenharmony_ci
218cb93a386Sopenharmony_ci        if (!jname   || !jname->size() ||
219cb93a386Sopenharmony_ci            !jfamily || !jfamily->size() ||
220cb93a386Sopenharmony_ci            !jstyle) {
221cb93a386Sopenharmony_ci            this->log(Logger::Level::kError, jfont, "Invalid font.");
222cb93a386Sopenharmony_ci            continue;
223cb93a386Sopenharmony_ci        }
224cb93a386Sopenharmony_ci
225cb93a386Sopenharmony_ci        fFonts.set(SkString(jname->begin(), jname->size()),
226cb93a386Sopenharmony_ci                  {
227cb93a386Sopenharmony_ci                      SkString(jfamily->begin(), jfamily->size()),
228cb93a386Sopenharmony_ci                      SkString( jstyle->begin(),  jstyle->size()),
229cb93a386Sopenharmony_ci                      jpath ? SkString(  jpath->begin(),   jpath->size()) : SkString(),
230cb93a386Sopenharmony_ci                      ParseDefault((*jfont)["ascent"] , 0.0f),
231cb93a386Sopenharmony_ci                      nullptr, // placeholder
232cb93a386Sopenharmony_ci                      SkCustomTypefaceBuilder()
233cb93a386Sopenharmony_ci                  });
234cb93a386Sopenharmony_ci    }
235cb93a386Sopenharmony_ci
236cb93a386Sopenharmony_ci    // Optional pass.
237cb93a386Sopenharmony_ci    if (jchars && (fFlags & Animation::Builder::kPreferEmbeddedFonts) &&
238cb93a386Sopenharmony_ci        this->resolveEmbeddedTypefaces(*jchars)) {
239cb93a386Sopenharmony_ci        return;
240cb93a386Sopenharmony_ci    }
241cb93a386Sopenharmony_ci
242cb93a386Sopenharmony_ci    // Native typeface resolution.
243cb93a386Sopenharmony_ci    if (this->resolveNativeTypefaces()) {
244cb93a386Sopenharmony_ci        return;
245cb93a386Sopenharmony_ci    }
246cb93a386Sopenharmony_ci
247cb93a386Sopenharmony_ci    // Embedded typeface fallback.
248cb93a386Sopenharmony_ci    if (jchars && !(fFlags & Animation::Builder::kPreferEmbeddedFonts) &&
249cb93a386Sopenharmony_ci        this->resolveEmbeddedTypefaces(*jchars)) {
250cb93a386Sopenharmony_ci    }
251cb93a386Sopenharmony_ci}
252cb93a386Sopenharmony_ci
253cb93a386Sopenharmony_cibool AnimationBuilder::resolveNativeTypefaces() {
254cb93a386Sopenharmony_ci    bool has_unresolved = false;
255cb93a386Sopenharmony_ci
256cb93a386Sopenharmony_ci    fFonts.foreach([&](const SkString& name, FontInfo* finfo) {
257cb93a386Sopenharmony_ci        SkASSERT(finfo);
258cb93a386Sopenharmony_ci
259cb93a386Sopenharmony_ci        if (finfo->fTypeface) {
260cb93a386Sopenharmony_ci            // Already resolved from glyph paths.
261cb93a386Sopenharmony_ci            return;
262cb93a386Sopenharmony_ci        }
263cb93a386Sopenharmony_ci
264cb93a386Sopenharmony_ci        const auto& fmgr = fLazyFontMgr.get();
265cb93a386Sopenharmony_ci
266cb93a386Sopenharmony_ci        // Typeface fallback order:
267cb93a386Sopenharmony_ci        //   1) externally-loaded font (provided by the embedder)
268cb93a386Sopenharmony_ci        //   2) system font (family/style)
269cb93a386Sopenharmony_ci        //   3) system default
270cb93a386Sopenharmony_ci
271cb93a386Sopenharmony_ci        finfo->fTypeface = fResourceProvider->loadTypeface(name.c_str(), finfo->fPath.c_str());
272cb93a386Sopenharmony_ci
273cb93a386Sopenharmony_ci        // legacy API fallback
274cb93a386Sopenharmony_ci        // TODO: remove after client migration
275cb93a386Sopenharmony_ci        if (!finfo->fTypeface) {
276cb93a386Sopenharmony_ci            finfo->fTypeface = fmgr->makeFromData(
277cb93a386Sopenharmony_ci                    fResourceProvider->loadFont(name.c_str(), finfo->fPath.c_str()));
278cb93a386Sopenharmony_ci        }
279cb93a386Sopenharmony_ci
280cb93a386Sopenharmony_ci        if (!finfo->fTypeface) {
281cb93a386Sopenharmony_ci            finfo->fTypeface.reset(fmgr->matchFamilyStyle(finfo->fFamily.c_str(),
282cb93a386Sopenharmony_ci                                                          FontStyle(this, finfo->fStyle.c_str())));
283cb93a386Sopenharmony_ci
284cb93a386Sopenharmony_ci            if (!finfo->fTypeface) {
285cb93a386Sopenharmony_ci                this->log(Logger::Level::kError, nullptr, "Could not create typeface for %s|%s.",
286cb93a386Sopenharmony_ci                          finfo->fFamily.c_str(), finfo->fStyle.c_str());
287cb93a386Sopenharmony_ci                // Last resort.
288cb93a386Sopenharmony_ci                finfo->fTypeface = fmgr->legacyMakeTypeface(nullptr,
289cb93a386Sopenharmony_ci                                                            FontStyle(this, finfo->fStyle.c_str()));
290cb93a386Sopenharmony_ci
291cb93a386Sopenharmony_ci                has_unresolved |= !finfo->fTypeface;
292cb93a386Sopenharmony_ci            }
293cb93a386Sopenharmony_ci        }
294cb93a386Sopenharmony_ci    });
295cb93a386Sopenharmony_ci
296cb93a386Sopenharmony_ci    return !has_unresolved;
297cb93a386Sopenharmony_ci}
298cb93a386Sopenharmony_ci
299cb93a386Sopenharmony_cibool AnimationBuilder::resolveEmbeddedTypefaces(const skjson::ArrayValue& jchars) {
300cb93a386Sopenharmony_ci    // Optional array of glyphs, to be associated with one of the declared fonts. E.g.
301cb93a386Sopenharmony_ci    // "chars": [
302cb93a386Sopenharmony_ci    //     {
303cb93a386Sopenharmony_ci    //         "ch": "t",
304cb93a386Sopenharmony_ci    //         "data": {
305cb93a386Sopenharmony_ci    //             "shapes": [...]        // shape-layer-like geometry
306cb93a386Sopenharmony_ci    //         },
307cb93a386Sopenharmony_ci    //         "fFamily": "Roboto",       // part of the font key
308cb93a386Sopenharmony_ci    //         "size": 50,                // apparently ignored
309cb93a386Sopenharmony_ci    //         "style": "Regular",        // part of the font key
310cb93a386Sopenharmony_ci    //         "w": 32.67                 // width/advance (1/100 units)
311cb93a386Sopenharmony_ci    //    }
312cb93a386Sopenharmony_ci    // ]
313cb93a386Sopenharmony_ci    FontInfo* current_font = nullptr;
314cb93a386Sopenharmony_ci
315cb93a386Sopenharmony_ci    for (const skjson::ObjectValue* jchar : jchars) {
316cb93a386Sopenharmony_ci        if (!jchar) {
317cb93a386Sopenharmony_ci            continue;
318cb93a386Sopenharmony_ci        }
319cb93a386Sopenharmony_ci
320cb93a386Sopenharmony_ci        const skjson::StringValue* jch = (*jchar)["ch"];
321cb93a386Sopenharmony_ci        if (!jch) {
322cb93a386Sopenharmony_ci            continue;
323cb93a386Sopenharmony_ci        }
324cb93a386Sopenharmony_ci
325cb93a386Sopenharmony_ci        const skjson::StringValue* jfamily = (*jchar)["fFamily"];
326cb93a386Sopenharmony_ci        const skjson::StringValue* jstyle  = (*jchar)["style"]; // "style", not "fStyle"...
327cb93a386Sopenharmony_ci
328cb93a386Sopenharmony_ci        const auto* ch_ptr = jch->begin();
329cb93a386Sopenharmony_ci        const auto  ch_len = jch->size();
330cb93a386Sopenharmony_ci
331cb93a386Sopenharmony_ci        if (!jfamily || !jstyle || (SkUTF::CountUTF8(ch_ptr, ch_len) != 1)) {
332cb93a386Sopenharmony_ci            this->log(Logger::Level::kError, jchar, "Invalid glyph.");
333cb93a386Sopenharmony_ci            continue;
334cb93a386Sopenharmony_ci        }
335cb93a386Sopenharmony_ci
336cb93a386Sopenharmony_ci        const auto uni = SkUTF::NextUTF8(&ch_ptr, ch_ptr + ch_len);
337cb93a386Sopenharmony_ci        SkASSERT(uni != -1);
338cb93a386Sopenharmony_ci        if (!SkTFitsIn<SkGlyphID>(uni)) {
339cb93a386Sopenharmony_ci            // Custom font keys are SkGlyphIDs.  We could implement a remapping scheme if needed,
340cb93a386Sopenharmony_ci            // but for now direct mapping seems to work well enough.
341cb93a386Sopenharmony_ci            this->log(Logger::Level::kError, jchar, "Unsupported glyph ID.");
342cb93a386Sopenharmony_ci            continue;
343cb93a386Sopenharmony_ci        }
344cb93a386Sopenharmony_ci        const auto glyph_id = SkTo<SkGlyphID>(uni);
345cb93a386Sopenharmony_ci
346cb93a386Sopenharmony_ci        const auto* family = jfamily->begin();
347cb93a386Sopenharmony_ci        const auto* style  = jstyle->begin();
348cb93a386Sopenharmony_ci
349cb93a386Sopenharmony_ci        // Locate (and cache) the font info. Unlike text nodes, glyphs reference the font by
350cb93a386Sopenharmony_ci        // (family, style) -- not by name :(  For now this performs a linear search over *all*
351cb93a386Sopenharmony_ci        // fonts: generally there are few of them, and glyph definitions are font-clustered.
352cb93a386Sopenharmony_ci        // If problematic, we can refactor as a two-level hashmap.
353cb93a386Sopenharmony_ci        if (!current_font || !current_font->matches(family, style)) {
354cb93a386Sopenharmony_ci            current_font = nullptr;
355cb93a386Sopenharmony_ci            fFonts.foreach([&](const SkString& name, FontInfo* finfo) {
356cb93a386Sopenharmony_ci                if (finfo->matches(family, style)) {
357cb93a386Sopenharmony_ci                    current_font = finfo;
358cb93a386Sopenharmony_ci                    // TODO: would be nice to break early here...
359cb93a386Sopenharmony_ci                }
360cb93a386Sopenharmony_ci            });
361cb93a386Sopenharmony_ci            if (!current_font) {
362cb93a386Sopenharmony_ci                this->log(Logger::Level::kError, nullptr,
363cb93a386Sopenharmony_ci                          "Font not found for codepoint (%d, %s, %s).", uni, family, style);
364cb93a386Sopenharmony_ci                continue;
365cb93a386Sopenharmony_ci            }
366cb93a386Sopenharmony_ci        }
367cb93a386Sopenharmony_ci
368cb93a386Sopenharmony_ci        SkPath path;
369cb93a386Sopenharmony_ci        if (!parse_glyph_path((*jchar)["data"], this, &path)) {
370cb93a386Sopenharmony_ci            continue;
371cb93a386Sopenharmony_ci        }
372cb93a386Sopenharmony_ci
373cb93a386Sopenharmony_ci        const auto advance = ParseDefault((*jchar)["w"], 0.0f);
374cb93a386Sopenharmony_ci
375cb93a386Sopenharmony_ci        // Interestingly, glyph paths are defined in a percentage-based space,
376cb93a386Sopenharmony_ci        // regardless of declared glyph size...
377cb93a386Sopenharmony_ci        static constexpr float kPtScale = 0.01f;
378cb93a386Sopenharmony_ci
379cb93a386Sopenharmony_ci        // Normalize the path and advance for 1pt.
380cb93a386Sopenharmony_ci        path.transform(SkMatrix::Scale(kPtScale, kPtScale));
381cb93a386Sopenharmony_ci
382cb93a386Sopenharmony_ci        current_font->fCustomBuilder.setGlyph(glyph_id, advance * kPtScale, path);
383cb93a386Sopenharmony_ci    }
384cb93a386Sopenharmony_ci
385cb93a386Sopenharmony_ci    // Final pass to commit custom typefaces.
386cb93a386Sopenharmony_ci    auto has_unresolved = false;
387cb93a386Sopenharmony_ci    fFonts.foreach([&has_unresolved](const SkString&, FontInfo* finfo) {
388cb93a386Sopenharmony_ci        if (finfo->fTypeface) {
389cb93a386Sopenharmony_ci            return; // already resolved
390cb93a386Sopenharmony_ci        }
391cb93a386Sopenharmony_ci
392cb93a386Sopenharmony_ci        finfo->fTypeface = finfo->fCustomBuilder.detach();
393cb93a386Sopenharmony_ci
394cb93a386Sopenharmony_ci        has_unresolved |= !finfo->fTypeface;
395cb93a386Sopenharmony_ci    });
396cb93a386Sopenharmony_ci
397cb93a386Sopenharmony_ci    return !has_unresolved;
398cb93a386Sopenharmony_ci}
399cb93a386Sopenharmony_ci
400cb93a386Sopenharmony_cisk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectValue& jlayer,
401cb93a386Sopenharmony_ci                                                          LayerInfo*) const {
402cb93a386Sopenharmony_ci    return this->attachDiscardableAdapter<TextAdapter>(jlayer,
403cb93a386Sopenharmony_ci                                                       this,
404cb93a386Sopenharmony_ci                                                       fLazyFontMgr.getMaybeNull(),
405cb93a386Sopenharmony_ci                                                       fLogger);
406cb93a386Sopenharmony_ci}
407cb93a386Sopenharmony_ci#endif
408cb93a386Sopenharmony_ci
409cb93a386Sopenharmony_ciconst AnimationBuilder::FontInfo* AnimationBuilder::findFont(const SkString& font_name) const {
410cb93a386Sopenharmony_ci    return fFonts.find(font_name);
411cb93a386Sopenharmony_ci}
412cb93a386Sopenharmony_ci
413cb93a386Sopenharmony_ci} // namespace internal
414cb93a386Sopenharmony_ci} // namespace skottie
415