1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2024 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/utils/TextPreshape.h"
9*c8dee2aaSAndroid Build Coastguard Worker
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkData.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkFont.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkFontMgr.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkMatrix.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPath.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPathTypes.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPoint.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkStream.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkString.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypes.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkDebug.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTPin.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTo.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/include/ExternalLayer.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/include/Skottie.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/include/SkottieProperty.h"
26*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/include/TextShaper.h"
27*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/SkottieJson.h"
28*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/SkottiePriv.h"
29*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/text/TextValue.h"
30*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skresources/include/SkResources.h"
31*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skshaper/include/SkShaper_factory.h"
32*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkArenaAlloc.h"
33*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkUTF.h"
34*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkGeometry.h"
35*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkPathPriv.h"
36*c8dee2aaSAndroid Build Coastguard Worker #include "src/utils/SkJSON.h"
37*c8dee2aaSAndroid Build Coastguard Worker
38*c8dee2aaSAndroid Build Coastguard Worker #include <cstddef>
39*c8dee2aaSAndroid Build Coastguard Worker #include <iostream>
40*c8dee2aaSAndroid Build Coastguard Worker #include <string>
41*c8dee2aaSAndroid Build Coastguard Worker #include <string_view>
42*c8dee2aaSAndroid Build Coastguard Worker #include <tuple>
43*c8dee2aaSAndroid Build Coastguard Worker #include <unordered_map>
44*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
45*c8dee2aaSAndroid Build Coastguard Worker #include <vector>
46*c8dee2aaSAndroid Build Coastguard Worker
47*c8dee2aaSAndroid Build Coastguard Worker using ResourceProvider = skresources::ResourceProvider;
48*c8dee2aaSAndroid Build Coastguard Worker
49*c8dee2aaSAndroid Build Coastguard Worker using skjson::ArrayValue;
50*c8dee2aaSAndroid Build Coastguard Worker using skjson::BoolValue;
51*c8dee2aaSAndroid Build Coastguard Worker using skjson::NumberValue;
52*c8dee2aaSAndroid Build Coastguard Worker using skjson::ObjectValue;
53*c8dee2aaSAndroid Build Coastguard Worker using skjson::StringValue;
54*c8dee2aaSAndroid Build Coastguard Worker using skjson::Value;
55*c8dee2aaSAndroid Build Coastguard Worker
56*c8dee2aaSAndroid Build Coastguard Worker namespace {
57*c8dee2aaSAndroid Build Coastguard Worker
preshapedFontName(const std::string_view & fontName)58*c8dee2aaSAndroid Build Coastguard Worker SkString preshapedFontName(const std::string_view& fontName) {
59*c8dee2aaSAndroid Build Coastguard Worker return SkStringPrintf("%s_preshaped", fontName.data());
60*c8dee2aaSAndroid Build Coastguard Worker }
61*c8dee2aaSAndroid Build Coastguard Worker
pathToLottie(const SkPath & path,SkArenaAlloc & alloc)62*c8dee2aaSAndroid Build Coastguard Worker Value pathToLottie(const SkPath& path, SkArenaAlloc& alloc) {
63*c8dee2aaSAndroid Build Coastguard Worker // Lottie paths are single-contour vectors of cubic segments, stored as
64*c8dee2aaSAndroid Build Coastguard Worker // (vertex, in_tangent, out_tangent) tuples.
65*c8dee2aaSAndroid Build Coastguard Worker // A usual Skia cubic segment (p0, c0, c1, p1) corresponds to Lottie's
66*c8dee2aaSAndroid Build Coastguard Worker // (vertex[0], out_tan[0], in_tan[1], vertex[1]).
67*c8dee2aaSAndroid Build Coastguard Worker // Tangent control points are stored in separate arrays, using relative coordinates.
68*c8dee2aaSAndroid Build Coastguard Worker struct Contour {
69*c8dee2aaSAndroid Build Coastguard Worker std::vector<SkPoint> verts, in_tan, out_tan;
70*c8dee2aaSAndroid Build Coastguard Worker bool closed = false;
71*c8dee2aaSAndroid Build Coastguard Worker
72*c8dee2aaSAndroid Build Coastguard Worker void add(const SkPoint& v, const SkPoint& i, const SkPoint& o) {
73*c8dee2aaSAndroid Build Coastguard Worker verts.push_back(v);
74*c8dee2aaSAndroid Build Coastguard Worker in_tan.push_back(i);
75*c8dee2aaSAndroid Build Coastguard Worker out_tan.push_back(o);
76*c8dee2aaSAndroid Build Coastguard Worker }
77*c8dee2aaSAndroid Build Coastguard Worker
78*c8dee2aaSAndroid Build Coastguard Worker size_t size() const {
79*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(verts.size() == in_tan.size());
80*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(verts.size() == out_tan.size());
81*c8dee2aaSAndroid Build Coastguard Worker return verts.size();
82*c8dee2aaSAndroid Build Coastguard Worker }
83*c8dee2aaSAndroid Build Coastguard Worker };
84*c8dee2aaSAndroid Build Coastguard Worker
85*c8dee2aaSAndroid Build Coastguard Worker std::vector<Contour> contours(1);
86*c8dee2aaSAndroid Build Coastguard Worker
87*c8dee2aaSAndroid Build Coastguard Worker for (const auto [verb, pts, weights] : SkPathPriv::Iterate(path)) {
88*c8dee2aaSAndroid Build Coastguard Worker switch (verb) {
89*c8dee2aaSAndroid Build Coastguard Worker case SkPathVerb::kMove:
90*c8dee2aaSAndroid Build Coastguard Worker if (!contours.back().verts.empty()) {
91*c8dee2aaSAndroid Build Coastguard Worker contours.emplace_back();
92*c8dee2aaSAndroid Build Coastguard Worker }
93*c8dee2aaSAndroid Build Coastguard Worker contours.back().add(pts[0], {0, 0}, {0, 0});
94*c8dee2aaSAndroid Build Coastguard Worker break;
95*c8dee2aaSAndroid Build Coastguard Worker case SkPathVerb::kClose:
96*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(contours.back().size() > 0);
97*c8dee2aaSAndroid Build Coastguard Worker contours.back().closed = true;
98*c8dee2aaSAndroid Build Coastguard Worker break;
99*c8dee2aaSAndroid Build Coastguard Worker case SkPathVerb::kLine:
100*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(contours.back().size() > 0);
101*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(pts[0] == contours.back().verts.back());
102*c8dee2aaSAndroid Build Coastguard Worker contours.back().add(pts[1], {0, 0}, {0, 0});
103*c8dee2aaSAndroid Build Coastguard Worker break;
104*c8dee2aaSAndroid Build Coastguard Worker case SkPathVerb::kQuad:
105*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(contours.back().size() > 0);
106*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(pts[0] == contours.back().verts.back());
107*c8dee2aaSAndroid Build Coastguard Worker SkPoint cubic[4];
108*c8dee2aaSAndroid Build Coastguard Worker SkConvertQuadToCubic(pts, cubic);
109*c8dee2aaSAndroid Build Coastguard Worker contours.back().out_tan.back() = cubic[1] - cubic[0];
110*c8dee2aaSAndroid Build Coastguard Worker contours.back().add(cubic[3], cubic[2] - cubic[3], {0, 0});
111*c8dee2aaSAndroid Build Coastguard Worker break;
112*c8dee2aaSAndroid Build Coastguard Worker case SkPathVerb::kCubic:
113*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(contours.back().size() > 0);
114*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(pts[0] == contours.back().verts.back());
115*c8dee2aaSAndroid Build Coastguard Worker contours.back().out_tan.back() = pts[1] - pts[0];
116*c8dee2aaSAndroid Build Coastguard Worker contours.back().add(pts[3], pts[2] - pts[3], {0, 0});
117*c8dee2aaSAndroid Build Coastguard Worker break;
118*c8dee2aaSAndroid Build Coastguard Worker case SkPathVerb::kConic:
119*c8dee2aaSAndroid Build Coastguard Worker SkDebugf("Unexpected conic verb!\n");
120*c8dee2aaSAndroid Build Coastguard Worker break;
121*c8dee2aaSAndroid Build Coastguard Worker }
122*c8dee2aaSAndroid Build Coastguard Worker }
123*c8dee2aaSAndroid Build Coastguard Worker
124*c8dee2aaSAndroid Build Coastguard Worker auto ptsToLottie = [](const std::vector<SkPoint> v, SkArenaAlloc& alloc) {
125*c8dee2aaSAndroid Build Coastguard Worker std::vector<Value> vec(v.size());
126*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < v.size(); ++i) {
127*c8dee2aaSAndroid Build Coastguard Worker Value fields[] = { NumberValue(v[i].fX), NumberValue(v[i].fY) };
128*c8dee2aaSAndroid Build Coastguard Worker vec[i] = ArrayValue(fields, std::size(fields), alloc);
129*c8dee2aaSAndroid Build Coastguard Worker }
130*c8dee2aaSAndroid Build Coastguard Worker
131*c8dee2aaSAndroid Build Coastguard Worker return ArrayValue(vec.data(), vec.size(), alloc);
132*c8dee2aaSAndroid Build Coastguard Worker };
133*c8dee2aaSAndroid Build Coastguard Worker
134*c8dee2aaSAndroid Build Coastguard Worker std::vector<Value> jcontours(contours.size());
135*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < contours.size(); ++i) {
136*c8dee2aaSAndroid Build Coastguard Worker const skjson::Member fields_k[] = {
137*c8dee2aaSAndroid Build Coastguard Worker { StringValue("v", alloc), ptsToLottie(contours[i].verts, alloc) },
138*c8dee2aaSAndroid Build Coastguard Worker { StringValue("i", alloc), ptsToLottie(contours[i].in_tan, alloc) },
139*c8dee2aaSAndroid Build Coastguard Worker { StringValue("o", alloc), ptsToLottie(contours[i].out_tan, alloc) },
140*c8dee2aaSAndroid Build Coastguard Worker { StringValue("c", alloc), BoolValue (contours[i].closed) },
141*c8dee2aaSAndroid Build Coastguard Worker };
142*c8dee2aaSAndroid Build Coastguard Worker
143*c8dee2aaSAndroid Build Coastguard Worker const skjson::Member fields_ks[] = {
144*c8dee2aaSAndroid Build Coastguard Worker { StringValue("a", alloc), NumberValue(0) },
145*c8dee2aaSAndroid Build Coastguard Worker { StringValue("k", alloc), ObjectValue(fields_k, std::size(fields_k), alloc) },
146*c8dee2aaSAndroid Build Coastguard Worker };
147*c8dee2aaSAndroid Build Coastguard Worker
148*c8dee2aaSAndroid Build Coastguard Worker const skjson::Member fields[] = {
149*c8dee2aaSAndroid Build Coastguard Worker { StringValue("ty" , alloc), StringValue("sh", alloc) },
150*c8dee2aaSAndroid Build Coastguard Worker { StringValue("hd" , alloc), BoolValue(false) },
151*c8dee2aaSAndroid Build Coastguard Worker { StringValue("ind", alloc), NumberValue(SkToInt(i)) },
152*c8dee2aaSAndroid Build Coastguard Worker { StringValue("ks" , alloc), ObjectValue(fields_ks, std::size(fields_ks), alloc) },
153*c8dee2aaSAndroid Build Coastguard Worker { StringValue("mn" , alloc), StringValue("ADBE Vector Shape - Group" , alloc) },
154*c8dee2aaSAndroid Build Coastguard Worker { StringValue("nm" , alloc), StringValue("_" , alloc) },
155*c8dee2aaSAndroid Build Coastguard Worker };
156*c8dee2aaSAndroid Build Coastguard Worker
157*c8dee2aaSAndroid Build Coastguard Worker jcontours[i] = ObjectValue(fields, std::size(fields), alloc);
158*c8dee2aaSAndroid Build Coastguard Worker }
159*c8dee2aaSAndroid Build Coastguard Worker
160*c8dee2aaSAndroid Build Coastguard Worker const skjson::Member fields_sh[] = {
161*c8dee2aaSAndroid Build Coastguard Worker { StringValue("ty" , alloc), StringValue("gr", alloc) },
162*c8dee2aaSAndroid Build Coastguard Worker { StringValue("hd" , alloc), BoolValue(false) },
163*c8dee2aaSAndroid Build Coastguard Worker { StringValue("bm" , alloc), NumberValue(0) },
164*c8dee2aaSAndroid Build Coastguard Worker { StringValue("it" , alloc), ArrayValue(jcontours.data(), jcontours.size(), alloc) },
165*c8dee2aaSAndroid Build Coastguard Worker { StringValue("mn" , alloc), StringValue("ADBE Vector Group" , alloc) },
166*c8dee2aaSAndroid Build Coastguard Worker { StringValue("nm" , alloc), StringValue("_" , alloc) },
167*c8dee2aaSAndroid Build Coastguard Worker };
168*c8dee2aaSAndroid Build Coastguard Worker
169*c8dee2aaSAndroid Build Coastguard Worker const Value shape = ObjectValue(fields_sh, std::size(fields_sh), alloc);
170*c8dee2aaSAndroid Build Coastguard Worker const skjson::Member fields_data[] = {
171*c8dee2aaSAndroid Build Coastguard Worker { StringValue("shapes" , alloc), ArrayValue(&shape, 1, alloc) },
172*c8dee2aaSAndroid Build Coastguard Worker };
173*c8dee2aaSAndroid Build Coastguard Worker
174*c8dee2aaSAndroid Build Coastguard Worker return ObjectValue(fields_data, std::size(fields_data), alloc);
175*c8dee2aaSAndroid Build Coastguard Worker }
176*c8dee2aaSAndroid Build Coastguard Worker
177*c8dee2aaSAndroid Build Coastguard Worker class GlyphCache {
178*c8dee2aaSAndroid Build Coastguard Worker public:
179*c8dee2aaSAndroid Build Coastguard Worker struct GlyphRec {
180*c8dee2aaSAndroid Build Coastguard Worker SkUnichar fID;
181*c8dee2aaSAndroid Build Coastguard Worker float fWidth;
182*c8dee2aaSAndroid Build Coastguard Worker SkPath fPath;
183*c8dee2aaSAndroid Build Coastguard Worker };
184*c8dee2aaSAndroid Build Coastguard Worker
addGlyph(const std::string_view & font_name,SkUnichar id,const SkFont & font,SkGlyphID glyph)185*c8dee2aaSAndroid Build Coastguard Worker void addGlyph(const std::string_view& font_name, SkUnichar id, const SkFont& font,
186*c8dee2aaSAndroid Build Coastguard Worker SkGlyphID glyph) {
187*c8dee2aaSAndroid Build Coastguard Worker std::vector<GlyphRec>& font_glyphs =
188*c8dee2aaSAndroid Build Coastguard Worker fFontGlyphs.emplace(font_name, std::vector<GlyphRec>()).first->second;
189*c8dee2aaSAndroid Build Coastguard Worker
190*c8dee2aaSAndroid Build Coastguard Worker // We don't expect a large number of glyphs, linear search should be fine.
191*c8dee2aaSAndroid Build Coastguard Worker for (const auto& rec : font_glyphs) {
192*c8dee2aaSAndroid Build Coastguard Worker if (rec.fID == id) {
193*c8dee2aaSAndroid Build Coastguard Worker return;
194*c8dee2aaSAndroid Build Coastguard Worker }
195*c8dee2aaSAndroid Build Coastguard Worker }
196*c8dee2aaSAndroid Build Coastguard Worker
197*c8dee2aaSAndroid Build Coastguard Worker SkPath path;
198*c8dee2aaSAndroid Build Coastguard Worker if (!font.getPath(glyph, &path)) {
199*c8dee2aaSAndroid Build Coastguard Worker // Only glyphs that can be represented as paths are supported for now, color glyphs are
200*c8dee2aaSAndroid Build Coastguard Worker // ignored. We could look into converting these to comp-based Lottie fonts if needed.
201*c8dee2aaSAndroid Build Coastguard Worker
202*c8dee2aaSAndroid Build Coastguard Worker // TODO: plumb a client-privided skottie::Logger for error reporting.
203*c8dee2aaSAndroid Build Coastguard Worker std::cerr << "Glyph ID %d could not be converted to a path, discarding.";
204*c8dee2aaSAndroid Build Coastguard Worker }
205*c8dee2aaSAndroid Build Coastguard Worker
206*c8dee2aaSAndroid Build Coastguard Worker float width;
207*c8dee2aaSAndroid Build Coastguard Worker font.getWidths(&glyph, 1, &width);
208*c8dee2aaSAndroid Build Coastguard Worker
209*c8dee2aaSAndroid Build Coastguard Worker // Lottie glyph shapes are always defined at a normalized size of 100.
210*c8dee2aaSAndroid Build Coastguard Worker const float scale = 100 / font.getSize();
211*c8dee2aaSAndroid Build Coastguard Worker
212*c8dee2aaSAndroid Build Coastguard Worker font_glyphs.push_back({
213*c8dee2aaSAndroid Build Coastguard Worker id,
214*c8dee2aaSAndroid Build Coastguard Worker width * scale,
215*c8dee2aaSAndroid Build Coastguard Worker path.makeTransform(SkMatrix::Scale(scale, scale))
216*c8dee2aaSAndroid Build Coastguard Worker });
217*c8dee2aaSAndroid Build Coastguard Worker }
218*c8dee2aaSAndroid Build Coastguard Worker
toLottie(SkArenaAlloc & alloc,const Value & orig_fonts) const219*c8dee2aaSAndroid Build Coastguard Worker std::tuple<Value, Value> toLottie(SkArenaAlloc& alloc, const Value& orig_fonts) const {
220*c8dee2aaSAndroid Build Coastguard Worker auto find_font_info = [&](const std::string& font_name) -> const ObjectValue* {
221*c8dee2aaSAndroid Build Coastguard Worker if (const ArrayValue* jlist = orig_fonts["list"]) {
222*c8dee2aaSAndroid Build Coastguard Worker for (const auto& jfont : *jlist) {
223*c8dee2aaSAndroid Build Coastguard Worker if (const StringValue* jname = jfont["fName"]) {
224*c8dee2aaSAndroid Build Coastguard Worker if (font_name == jname->begin()) {
225*c8dee2aaSAndroid Build Coastguard Worker return jfont;
226*c8dee2aaSAndroid Build Coastguard Worker }
227*c8dee2aaSAndroid Build Coastguard Worker }
228*c8dee2aaSAndroid Build Coastguard Worker }
229*c8dee2aaSAndroid Build Coastguard Worker }
230*c8dee2aaSAndroid Build Coastguard Worker
231*c8dee2aaSAndroid Build Coastguard Worker return nullptr;
232*c8dee2aaSAndroid Build Coastguard Worker };
233*c8dee2aaSAndroid Build Coastguard Worker
234*c8dee2aaSAndroid Build Coastguard Worker // Lottie glyph shape font data is stored in two arrays:
235*c8dee2aaSAndroid Build Coastguard Worker // - "fonts" holds font metadata (name, family, style, etc)
236*c8dee2aaSAndroid Build Coastguard Worker // - "chars" holds character data (char id, size, advance, path, etc)
237*c8dee2aaSAndroid Build Coastguard Worker // Individual chars are associated with specific fonts based on their
238*c8dee2aaSAndroid Build Coastguard Worker // "fFamily" and "style" props.
239*c8dee2aaSAndroid Build Coastguard Worker std::vector<Value> fonts, chars;
240*c8dee2aaSAndroid Build Coastguard Worker
241*c8dee2aaSAndroid Build Coastguard Worker for (const auto& font : fFontGlyphs) {
242*c8dee2aaSAndroid Build Coastguard Worker const ObjectValue* orig_font = find_font_info(font.first);
243*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(orig_font);
244*c8dee2aaSAndroid Build Coastguard Worker
245*c8dee2aaSAndroid Build Coastguard Worker // New font entry based on existing font data + updated name.
246*c8dee2aaSAndroid Build Coastguard Worker const SkString font_name = preshapedFontName(font.first);
247*c8dee2aaSAndroid Build Coastguard Worker orig_font->writable("fName", alloc) =
248*c8dee2aaSAndroid Build Coastguard Worker StringValue(font_name.c_str(), font_name.size(), alloc);
249*c8dee2aaSAndroid Build Coastguard Worker fonts.push_back(*orig_font);
250*c8dee2aaSAndroid Build Coastguard Worker
251*c8dee2aaSAndroid Build Coastguard Worker for (const auto& glyph : font.second) {
252*c8dee2aaSAndroid Build Coastguard Worker // New char entry.
253*c8dee2aaSAndroid Build Coastguard Worker char glyphid_as_utf8[SkUTF::kMaxBytesInUTF8Sequence];
254*c8dee2aaSAndroid Build Coastguard Worker size_t utf8_len = SkUTF::ToUTF8(glyph.fID, glyphid_as_utf8);
255*c8dee2aaSAndroid Build Coastguard Worker
256*c8dee2aaSAndroid Build Coastguard Worker skjson::Member fields[] = {
257*c8dee2aaSAndroid Build Coastguard Worker { StringValue("ch" , alloc), StringValue(glyphid_as_utf8, utf8_len, alloc)},
258*c8dee2aaSAndroid Build Coastguard Worker { StringValue("fFamily", alloc), (*orig_font)["fFamily"] },
259*c8dee2aaSAndroid Build Coastguard Worker { StringValue("style" , alloc), (*orig_font)["fStyle"] },
260*c8dee2aaSAndroid Build Coastguard Worker { StringValue("size" , alloc), NumberValue(100) },
261*c8dee2aaSAndroid Build Coastguard Worker { StringValue("w" , alloc), NumberValue(glyph.fWidth) },
262*c8dee2aaSAndroid Build Coastguard Worker { StringValue("data" , alloc), pathToLottie(glyph.fPath, alloc) },
263*c8dee2aaSAndroid Build Coastguard Worker };
264*c8dee2aaSAndroid Build Coastguard Worker
265*c8dee2aaSAndroid Build Coastguard Worker chars.push_back(ObjectValue(fields, std::size(fields), alloc));
266*c8dee2aaSAndroid Build Coastguard Worker }
267*c8dee2aaSAndroid Build Coastguard Worker }
268*c8dee2aaSAndroid Build Coastguard Worker
269*c8dee2aaSAndroid Build Coastguard Worker skjson::Member fonts_fields[] = {
270*c8dee2aaSAndroid Build Coastguard Worker { StringValue("list", alloc), ArrayValue(fonts.data(), fonts.size(), alloc) },
271*c8dee2aaSAndroid Build Coastguard Worker };
272*c8dee2aaSAndroid Build Coastguard Worker return std::make_tuple(ObjectValue(fonts_fields, std::size(fonts_fields), alloc),
273*c8dee2aaSAndroid Build Coastguard Worker ArrayValue(chars.data(), chars.size(), alloc));
274*c8dee2aaSAndroid Build Coastguard Worker }
275*c8dee2aaSAndroid Build Coastguard Worker
276*c8dee2aaSAndroid Build Coastguard Worker private:
277*c8dee2aaSAndroid Build Coastguard Worker std::unordered_map<std::string, std::vector<GlyphRec>> fFontGlyphs;
278*c8dee2aaSAndroid Build Coastguard Worker };
279*c8dee2aaSAndroid Build Coastguard Worker
280*c8dee2aaSAndroid Build Coastguard Worker class Preshaper {
281*c8dee2aaSAndroid Build Coastguard Worker public:
Preshaper(sk_sp<ResourceProvider> rp,sk_sp<SkFontMgr> fontmgr,sk_sp<SkShapers::Factory> sfact)282*c8dee2aaSAndroid Build Coastguard Worker Preshaper(sk_sp<ResourceProvider> rp, sk_sp<SkFontMgr> fontmgr, sk_sp<SkShapers::Factory> sfact)
283*c8dee2aaSAndroid Build Coastguard Worker : fFontMgr(fontmgr)
284*c8dee2aaSAndroid Build Coastguard Worker , fShapersFact(sfact)
285*c8dee2aaSAndroid Build Coastguard Worker , fBuilder(rp ? std::move(rp) : sk_make_sp<NullResourceProvider>(),
286*c8dee2aaSAndroid Build Coastguard Worker std::move(fontmgr),
287*c8dee2aaSAndroid Build Coastguard Worker nullptr, nullptr, nullptr, nullptr, nullptr,
288*c8dee2aaSAndroid Build Coastguard Worker std::move(sfact),
289*c8dee2aaSAndroid Build Coastguard Worker &fStats, {0, 0}, 1, 1, 0)
290*c8dee2aaSAndroid Build Coastguard Worker , fAlloc(4096)
291*c8dee2aaSAndroid Build Coastguard Worker {}
292*c8dee2aaSAndroid Build Coastguard Worker
preshape(const Value & jlottie)293*c8dee2aaSAndroid Build Coastguard Worker void preshape(const Value& jlottie) {
294*c8dee2aaSAndroid Build Coastguard Worker fBuilder.parseFonts(jlottie["fonts"], jlottie["chars"]);
295*c8dee2aaSAndroid Build Coastguard Worker
296*c8dee2aaSAndroid Build Coastguard Worker this->preshapeComp(jlottie);
297*c8dee2aaSAndroid Build Coastguard Worker if (const ArrayValue* jassets = jlottie["assets"]) {
298*c8dee2aaSAndroid Build Coastguard Worker for (const auto& jasset : *jassets) {
299*c8dee2aaSAndroid Build Coastguard Worker this->preshapeComp(jasset);
300*c8dee2aaSAndroid Build Coastguard Worker }
301*c8dee2aaSAndroid Build Coastguard Worker }
302*c8dee2aaSAndroid Build Coastguard Worker
303*c8dee2aaSAndroid Build Coastguard Worker const auto& [fonts, chars] = fGlyphCache.toLottie(fAlloc, jlottie["fonts"]);
304*c8dee2aaSAndroid Build Coastguard Worker
305*c8dee2aaSAndroid Build Coastguard Worker jlottie.as<ObjectValue>().writable("fonts", fAlloc) = fonts;
306*c8dee2aaSAndroid Build Coastguard Worker jlottie.as<ObjectValue>().writable("chars", fAlloc) = chars;
307*c8dee2aaSAndroid Build Coastguard Worker }
308*c8dee2aaSAndroid Build Coastguard Worker
309*c8dee2aaSAndroid Build Coastguard Worker private:
310*c8dee2aaSAndroid Build Coastguard Worker class NullResourceProvider final : public ResourceProvider {
load(const char[],const char[]) const311*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> load(const char[], const char[]) const override { return nullptr; }
312*c8dee2aaSAndroid Build Coastguard Worker };
313*c8dee2aaSAndroid Build Coastguard Worker
preshapeComp(const Value & jcomp)314*c8dee2aaSAndroid Build Coastguard Worker void preshapeComp(const Value& jcomp) {
315*c8dee2aaSAndroid Build Coastguard Worker if (const ArrayValue* jlayers = jcomp["layers"]) {
316*c8dee2aaSAndroid Build Coastguard Worker for (const auto& jlayer : *jlayers) {
317*c8dee2aaSAndroid Build Coastguard Worker this->preshapeLayer(jlayer);
318*c8dee2aaSAndroid Build Coastguard Worker }
319*c8dee2aaSAndroid Build Coastguard Worker }
320*c8dee2aaSAndroid Build Coastguard Worker }
321*c8dee2aaSAndroid Build Coastguard Worker
preshapeLayer(const Value & jlayer)322*c8dee2aaSAndroid Build Coastguard Worker void preshapeLayer(const Value& jlayer) {
323*c8dee2aaSAndroid Build Coastguard Worker static constexpr int kTextLayerType = 5;
324*c8dee2aaSAndroid Build Coastguard Worker if (skottie::ParseDefault<int>(jlayer["ty"], -1) != kTextLayerType) {
325*c8dee2aaSAndroid Build Coastguard Worker return;
326*c8dee2aaSAndroid Build Coastguard Worker }
327*c8dee2aaSAndroid Build Coastguard Worker
328*c8dee2aaSAndroid Build Coastguard Worker const ArrayValue* jtxts = jlayer["t"]["d"]["k"];
329*c8dee2aaSAndroid Build Coastguard Worker if (!jtxts) {
330*c8dee2aaSAndroid Build Coastguard Worker return;
331*c8dee2aaSAndroid Build Coastguard Worker }
332*c8dee2aaSAndroid Build Coastguard Worker
333*c8dee2aaSAndroid Build Coastguard Worker for (const auto& jtxt : *jtxts) {
334*c8dee2aaSAndroid Build Coastguard Worker const Value& jtxt_val = jtxt["s"];
335*c8dee2aaSAndroid Build Coastguard Worker
336*c8dee2aaSAndroid Build Coastguard Worker const StringValue* jfont_name = jtxt_val["f"];
337*c8dee2aaSAndroid Build Coastguard Worker skottie::TextValue txt_val;
338*c8dee2aaSAndroid Build Coastguard Worker if (!skottie::internal::Parse(jtxt_val, fBuilder , &txt_val) || !jfont_name) {
339*c8dee2aaSAndroid Build Coastguard Worker continue;
340*c8dee2aaSAndroid Build Coastguard Worker }
341*c8dee2aaSAndroid Build Coastguard Worker
342*c8dee2aaSAndroid Build Coastguard Worker const std::string_view font_name(jfont_name->begin(), jfont_name->size());
343*c8dee2aaSAndroid Build Coastguard Worker
344*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kMinSize = 0.1f,
345*c8dee2aaSAndroid Build Coastguard Worker kMaxSize = 1296.0f;
346*c8dee2aaSAndroid Build Coastguard Worker const skottie::Shaper::TextDesc text_desc = {
347*c8dee2aaSAndroid Build Coastguard Worker txt_val.fTypeface,
348*c8dee2aaSAndroid Build Coastguard Worker SkTPin(txt_val.fTextSize, kMinSize, kMaxSize),
349*c8dee2aaSAndroid Build Coastguard Worker SkTPin(txt_val.fMinTextSize, kMinSize, kMaxSize),
350*c8dee2aaSAndroid Build Coastguard Worker SkTPin(txt_val.fMaxTextSize, kMinSize, kMaxSize),
351*c8dee2aaSAndroid Build Coastguard Worker txt_val.fLineHeight,
352*c8dee2aaSAndroid Build Coastguard Worker txt_val.fLineShift,
353*c8dee2aaSAndroid Build Coastguard Worker txt_val.fAscent,
354*c8dee2aaSAndroid Build Coastguard Worker txt_val.fHAlign,
355*c8dee2aaSAndroid Build Coastguard Worker txt_val.fVAlign,
356*c8dee2aaSAndroid Build Coastguard Worker txt_val.fResize,
357*c8dee2aaSAndroid Build Coastguard Worker txt_val.fLineBreak,
358*c8dee2aaSAndroid Build Coastguard Worker txt_val.fDirection,
359*c8dee2aaSAndroid Build Coastguard Worker txt_val.fCapitalization,
360*c8dee2aaSAndroid Build Coastguard Worker txt_val.fMaxLines,
361*c8dee2aaSAndroid Build Coastguard Worker skottie::Shaper::Flags::kFragmentGlyphs |
362*c8dee2aaSAndroid Build Coastguard Worker skottie::Shaper::Flags::kTrackFragmentAdvanceAscent |
363*c8dee2aaSAndroid Build Coastguard Worker skottie::Shaper::Flags::kClusters,
364*c8dee2aaSAndroid Build Coastguard Worker txt_val.fLocale.isEmpty() ? nullptr : txt_val.fLocale.c_str(),
365*c8dee2aaSAndroid Build Coastguard Worker txt_val.fFontFamily.isEmpty() ? nullptr : txt_val.fFontFamily.c_str(),
366*c8dee2aaSAndroid Build Coastguard Worker };
367*c8dee2aaSAndroid Build Coastguard Worker
368*c8dee2aaSAndroid Build Coastguard Worker auto shape_result = skottie::Shaper::Shape(txt_val.fText, text_desc, txt_val.fBox,
369*c8dee2aaSAndroid Build Coastguard Worker fFontMgr, fShapersFact);
370*c8dee2aaSAndroid Build Coastguard Worker
371*c8dee2aaSAndroid Build Coastguard Worker auto shaped_glyph_info = [this](SkUnichar ch, const SkPoint& pos, float advance,
372*c8dee2aaSAndroid Build Coastguard Worker size_t line, size_t cluster) -> Value {
373*c8dee2aaSAndroid Build Coastguard Worker const NumberValue jpos[] = { NumberValue(pos.fX), NumberValue(pos.fY) };
374*c8dee2aaSAndroid Build Coastguard Worker char utf8[SkUTF::kMaxBytesInUTF8Sequence];
375*c8dee2aaSAndroid Build Coastguard Worker const size_t utf8_len = SkUTF::ToUTF8(ch, utf8);
376*c8dee2aaSAndroid Build Coastguard Worker
377*c8dee2aaSAndroid Build Coastguard Worker const skjson::Member fields[] = {
378*c8dee2aaSAndroid Build Coastguard Worker { StringValue("ch" , fAlloc), StringValue(utf8, utf8_len, fAlloc) },
379*c8dee2aaSAndroid Build Coastguard Worker { StringValue("ps" , fAlloc), ArrayValue(jpos, std::size(jpos), fAlloc) },
380*c8dee2aaSAndroid Build Coastguard Worker { StringValue("w" , fAlloc), NumberValue(advance) },
381*c8dee2aaSAndroid Build Coastguard Worker { StringValue("l" , fAlloc), NumberValue(SkToInt(line)) },
382*c8dee2aaSAndroid Build Coastguard Worker { StringValue("cix", fAlloc), NumberValue(SkToInt(cluster)) },
383*c8dee2aaSAndroid Build Coastguard Worker };
384*c8dee2aaSAndroid Build Coastguard Worker
385*c8dee2aaSAndroid Build Coastguard Worker return ObjectValue(fields, std::size(fields), fAlloc);
386*c8dee2aaSAndroid Build Coastguard Worker };
387*c8dee2aaSAndroid Build Coastguard Worker
388*c8dee2aaSAndroid Build Coastguard Worker std::vector<Value> shaped_info;
389*c8dee2aaSAndroid Build Coastguard Worker for (const auto& frag : shape_result.fFragments) {
390*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(frag.fGlyphs.fGlyphIDs.size() == 1);
391*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(frag.fGlyphs.fClusters.size() == frag.fGlyphs.fGlyphIDs.size());
392*c8dee2aaSAndroid Build Coastguard Worker size_t offset = 0;
393*c8dee2aaSAndroid Build Coastguard Worker for (const auto& runrec : frag.fGlyphs.fRuns) {
394*c8dee2aaSAndroid Build Coastguard Worker const SkGlyphID* glyphs = frag.fGlyphs.fGlyphIDs.data() + offset;
395*c8dee2aaSAndroid Build Coastguard Worker const SkPoint* glyph_pos = frag.fGlyphs.fGlyphPos.data() + offset;
396*c8dee2aaSAndroid Build Coastguard Worker const size_t* clusters = frag.fGlyphs.fClusters.data() + offset;
397*c8dee2aaSAndroid Build Coastguard Worker const char* end_utf8 = txt_val.fText.c_str() + txt_val.fText.size();
398*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < runrec.fSize; ++i) {
399*c8dee2aaSAndroid Build Coastguard Worker // TODO: we are only considering the fist code point in the cluster,
400*c8dee2aaSAndroid Build Coastguard Worker // similar to how Lottie handles custom/path-based fonts at the moment.
401*c8dee2aaSAndroid Build Coastguard Worker // To correctly handle larger clusters, we'll have to check for collisions
402*c8dee2aaSAndroid Build Coastguard Worker // and potentially allocate a synthetic glyph IDs. TBD.
403*c8dee2aaSAndroid Build Coastguard Worker const char* ch_utf8 = txt_val.fText.c_str() + clusters[i];
404*c8dee2aaSAndroid Build Coastguard Worker const SkUnichar ch = SkUTF::NextUTF8(&ch_utf8, end_utf8);
405*c8dee2aaSAndroid Build Coastguard Worker
406*c8dee2aaSAndroid Build Coastguard Worker fGlyphCache.addGlyph(font_name, ch, runrec.fFont, glyphs[i]);
407*c8dee2aaSAndroid Build Coastguard Worker shaped_info.push_back(shaped_glyph_info(ch,
408*c8dee2aaSAndroid Build Coastguard Worker frag.fOrigin + glyph_pos[i],
409*c8dee2aaSAndroid Build Coastguard Worker frag.fAdvance,
410*c8dee2aaSAndroid Build Coastguard Worker frag.fLineIndex,
411*c8dee2aaSAndroid Build Coastguard Worker clusters[i]));
412*c8dee2aaSAndroid Build Coastguard Worker }
413*c8dee2aaSAndroid Build Coastguard Worker offset += runrec.fSize;
414*c8dee2aaSAndroid Build Coastguard Worker }
415*c8dee2aaSAndroid Build Coastguard Worker }
416*c8dee2aaSAndroid Build Coastguard Worker
417*c8dee2aaSAndroid Build Coastguard Worker // Preshaped glyphs.
418*c8dee2aaSAndroid Build Coastguard Worker jtxt_val.as<ObjectValue>().writable("gl", fAlloc) =
419*c8dee2aaSAndroid Build Coastguard Worker ArrayValue(shaped_info.data(), shaped_info.size(), fAlloc);
420*c8dee2aaSAndroid Build Coastguard Worker // Effecive size for preshaped glyphs, accounting for auto-sizing scale.
421*c8dee2aaSAndroid Build Coastguard Worker jtxt_val.as<ObjectValue>().writable("gs", fAlloc) =
422*c8dee2aaSAndroid Build Coastguard Worker NumberValue(text_desc.fTextSize * shape_result.fScale);
423*c8dee2aaSAndroid Build Coastguard Worker // Updated font name.
424*c8dee2aaSAndroid Build Coastguard Worker jtxt_val.as<ObjectValue>().writable("f", fAlloc) =
425*c8dee2aaSAndroid Build Coastguard Worker StringValue(preshapedFontName(font_name).c_str(), fAlloc);
426*c8dee2aaSAndroid Build Coastguard Worker }
427*c8dee2aaSAndroid Build Coastguard Worker }
428*c8dee2aaSAndroid Build Coastguard Worker
429*c8dee2aaSAndroid Build Coastguard Worker const sk_sp<SkFontMgr> fFontMgr;
430*c8dee2aaSAndroid Build Coastguard Worker const sk_sp<SkShapers::Factory> fShapersFact;
431*c8dee2aaSAndroid Build Coastguard Worker skottie::Animation::Builder::Stats fStats;
432*c8dee2aaSAndroid Build Coastguard Worker skottie::internal::AnimationBuilder fBuilder;
433*c8dee2aaSAndroid Build Coastguard Worker SkArenaAlloc fAlloc;
434*c8dee2aaSAndroid Build Coastguard Worker GlyphCache fGlyphCache;
435*c8dee2aaSAndroid Build Coastguard Worker };
436*c8dee2aaSAndroid Build Coastguard Worker
437*c8dee2aaSAndroid Build Coastguard Worker } // namespace
438*c8dee2aaSAndroid Build Coastguard Worker
439*c8dee2aaSAndroid Build Coastguard Worker namespace skottie_utils {
440*c8dee2aaSAndroid Build Coastguard Worker
Preshape(const char * json,size_t size,SkWStream * stream,const sk_sp<SkFontMgr> & fmgr,const sk_sp<SkShapers::Factory> & sfact,const sk_sp<skresources::ResourceProvider> & rp)441*c8dee2aaSAndroid Build Coastguard Worker bool Preshape(const char* json, size_t size, SkWStream* stream,
442*c8dee2aaSAndroid Build Coastguard Worker const sk_sp<SkFontMgr>& fmgr,
443*c8dee2aaSAndroid Build Coastguard Worker const sk_sp<SkShapers::Factory>& sfact,
444*c8dee2aaSAndroid Build Coastguard Worker const sk_sp<skresources::ResourceProvider>& rp) {
445*c8dee2aaSAndroid Build Coastguard Worker skjson::DOM dom(json, size);
446*c8dee2aaSAndroid Build Coastguard Worker if (!dom.root().is<skjson::ObjectValue>()) {
447*c8dee2aaSAndroid Build Coastguard Worker return false;
448*c8dee2aaSAndroid Build Coastguard Worker }
449*c8dee2aaSAndroid Build Coastguard Worker
450*c8dee2aaSAndroid Build Coastguard Worker Preshaper preshaper(rp, fmgr, sfact);
451*c8dee2aaSAndroid Build Coastguard Worker
452*c8dee2aaSAndroid Build Coastguard Worker preshaper.preshape(dom.root());
453*c8dee2aaSAndroid Build Coastguard Worker
454*c8dee2aaSAndroid Build Coastguard Worker stream->writeText(dom.root().toString().c_str());
455*c8dee2aaSAndroid Build Coastguard Worker
456*c8dee2aaSAndroid Build Coastguard Worker return true;
457*c8dee2aaSAndroid Build Coastguard Worker }
458*c8dee2aaSAndroid Build Coastguard Worker
Preshape(const sk_sp<SkData> & json,SkWStream * stream,const sk_sp<SkFontMgr> & fmgr,const sk_sp<SkShapers::Factory> & sfact,const sk_sp<ResourceProvider> & rp)459*c8dee2aaSAndroid Build Coastguard Worker bool Preshape(const sk_sp<SkData>& json, SkWStream* stream,
460*c8dee2aaSAndroid Build Coastguard Worker const sk_sp<SkFontMgr>& fmgr,
461*c8dee2aaSAndroid Build Coastguard Worker const sk_sp<SkShapers::Factory>& sfact,
462*c8dee2aaSAndroid Build Coastguard Worker const sk_sp<ResourceProvider>& rp) {
463*c8dee2aaSAndroid Build Coastguard Worker return Preshape(static_cast<const char*>(json->data()), json->size(), stream, fmgr, sfact, rp);
464*c8dee2aaSAndroid Build Coastguard Worker }
465*c8dee2aaSAndroid Build Coastguard Worker
466*c8dee2aaSAndroid Build Coastguard Worker } // namespace skottie_utils
467