xref: /aosp_15_r20/external/skia/modules/skottie/src/text/Font.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2022 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/src/text/Font.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkMatrix.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPath.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRect.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSize.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypeface.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTFitsIn.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTo.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/SkottieJson.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/SkottiePriv.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGPath.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGTransform.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkUTF.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "src/utils/SkJSON.h"
23*c8dee2aaSAndroid Build Coastguard Worker 
24*c8dee2aaSAndroid Build Coastguard Worker namespace skottie::internal {
25*c8dee2aaSAndroid Build Coastguard Worker 
parseGlyph(const AnimationBuilder * abuilder,const skjson::ObjectValue & jchar)26*c8dee2aaSAndroid Build Coastguard Worker bool CustomFont::Builder::parseGlyph(const AnimationBuilder* abuilder,
27*c8dee2aaSAndroid Build Coastguard Worker                                      const skjson::ObjectValue& jchar) {
28*c8dee2aaSAndroid Build Coastguard Worker     // Glyph encoding:
29*c8dee2aaSAndroid Build Coastguard Worker     //     {
30*c8dee2aaSAndroid Build Coastguard Worker     //         "ch": "t",
31*c8dee2aaSAndroid Build Coastguard Worker     //         "data": <glyph data>,  // Glyph path or composition data
32*c8dee2aaSAndroid Build Coastguard Worker     //         "size": 50,            // apparently ignored
33*c8dee2aaSAndroid Build Coastguard Worker     //         "w": 32.67,            // width/advance (1/100 units)
34*c8dee2aaSAndroid Build Coastguard Worker     //         "t": 1                 // Marker for composition glyphs only.
35*c8dee2aaSAndroid Build Coastguard Worker     //     }
36*c8dee2aaSAndroid Build Coastguard Worker     const skjson::StringValue* jch   = jchar["ch"];
37*c8dee2aaSAndroid Build Coastguard Worker     const skjson::ObjectValue* jdata = jchar["data"];
38*c8dee2aaSAndroid Build Coastguard Worker     if (!jch || !jdata) {
39*c8dee2aaSAndroid Build Coastguard Worker         return false;
40*c8dee2aaSAndroid Build Coastguard Worker     }
41*c8dee2aaSAndroid Build Coastguard Worker 
42*c8dee2aaSAndroid Build Coastguard Worker     const auto* ch_ptr = jch->begin();
43*c8dee2aaSAndroid Build Coastguard Worker     const auto  ch_len = jch->size();
44*c8dee2aaSAndroid Build Coastguard Worker     if (SkUTF::CountUTF8(ch_ptr, ch_len) != 1) {
45*c8dee2aaSAndroid Build Coastguard Worker         return false;
46*c8dee2aaSAndroid Build Coastguard Worker     }
47*c8dee2aaSAndroid Build Coastguard Worker 
48*c8dee2aaSAndroid Build Coastguard Worker     const auto uni = SkUTF::NextUTF8(&ch_ptr, ch_ptr + ch_len);
49*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(uni != -1);
50*c8dee2aaSAndroid Build Coastguard Worker     if (!SkTFitsIn<SkGlyphID>(uni)) {
51*c8dee2aaSAndroid Build Coastguard Worker         // Custom font keys are SkGlyphIDs.  We could implement a remapping scheme if needed,
52*c8dee2aaSAndroid Build Coastguard Worker         // but for now direct mapping seems to work well enough.
53*c8dee2aaSAndroid Build Coastguard Worker         return false;
54*c8dee2aaSAndroid Build Coastguard Worker     }
55*c8dee2aaSAndroid Build Coastguard Worker     const auto glyph_id = SkTo<SkGlyphID>(uni);
56*c8dee2aaSAndroid Build Coastguard Worker 
57*c8dee2aaSAndroid Build Coastguard Worker     // Normalize the path and advance for 1pt.
58*c8dee2aaSAndroid Build Coastguard Worker     static constexpr float kPtScale = 0.01f;
59*c8dee2aaSAndroid Build Coastguard Worker     const auto advance = ParseDefault(jchar["w"], 0.0f) * kPtScale;
60*c8dee2aaSAndroid Build Coastguard Worker 
61*c8dee2aaSAndroid Build Coastguard Worker     // Custom glyphs are either compositions...
62*c8dee2aaSAndroid Build Coastguard Worker     SkSize glyph_size;
63*c8dee2aaSAndroid Build Coastguard Worker     if (auto comp_node = ParseGlyphComp(abuilder, *jdata, &glyph_size)) {
64*c8dee2aaSAndroid Build Coastguard Worker         // With glyph comps, we use the SkCustomTypeface only for shaping -- not for rendering.
65*c8dee2aaSAndroid Build Coastguard Worker         // We still need accurate glyph bounds though, for visual alignment.
66*c8dee2aaSAndroid Build Coastguard Worker 
67*c8dee2aaSAndroid Build Coastguard Worker         // TODO: This assumes the glyph origin is always in the lower-left corner.
68*c8dee2aaSAndroid Build Coastguard Worker         // Lottie may need to add an origin property, to allow designers full control over
69*c8dee2aaSAndroid Build Coastguard Worker         // glyph comp positioning.
70*c8dee2aaSAndroid Build Coastguard Worker         const auto glyph_bounds = SkRect::MakeLTRB(0, -glyph_size.fHeight, glyph_size.fWidth, 0);
71*c8dee2aaSAndroid Build Coastguard Worker         fCustomBuilder.setGlyph(glyph_id, advance, SkPath::Rect(glyph_bounds));
72*c8dee2aaSAndroid Build Coastguard Worker 
73*c8dee2aaSAndroid Build Coastguard Worker         // Rendering is handled explicitly, post shaping,
74*c8dee2aaSAndroid Build Coastguard Worker         // based on info tracked in this GlyphCompMap.
75*c8dee2aaSAndroid Build Coastguard Worker         fGlyphComps.set(glyph_id, std::move(comp_node));
76*c8dee2aaSAndroid Build Coastguard Worker 
77*c8dee2aaSAndroid Build Coastguard Worker         return true;
78*c8dee2aaSAndroid Build Coastguard Worker     }
79*c8dee2aaSAndroid Build Coastguard Worker 
80*c8dee2aaSAndroid Build Coastguard Worker     // ... or paths.
81*c8dee2aaSAndroid Build Coastguard Worker     SkPath path;
82*c8dee2aaSAndroid Build Coastguard Worker     if (!ParseGlyphPath(abuilder, *jdata, &path)) {
83*c8dee2aaSAndroid Build Coastguard Worker         return false;
84*c8dee2aaSAndroid Build Coastguard Worker     }
85*c8dee2aaSAndroid Build Coastguard Worker 
86*c8dee2aaSAndroid Build Coastguard Worker     path.transform(SkMatrix::Scale(kPtScale, kPtScale));
87*c8dee2aaSAndroid Build Coastguard Worker 
88*c8dee2aaSAndroid Build Coastguard Worker     fCustomBuilder.setGlyph(glyph_id, advance, path);
89*c8dee2aaSAndroid Build Coastguard Worker 
90*c8dee2aaSAndroid Build Coastguard Worker     return true;
91*c8dee2aaSAndroid Build Coastguard Worker }
92*c8dee2aaSAndroid Build Coastguard Worker 
ParseGlyphPath(const skottie::internal::AnimationBuilder * abuilder,const skjson::ObjectValue & jdata,SkPath * path)93*c8dee2aaSAndroid Build Coastguard Worker bool CustomFont::Builder::ParseGlyphPath(const skottie::internal::AnimationBuilder* abuilder,
94*c8dee2aaSAndroid Build Coastguard Worker                                          const skjson::ObjectValue& jdata,
95*c8dee2aaSAndroid Build Coastguard Worker                                          SkPath* path) {
96*c8dee2aaSAndroid Build Coastguard Worker     // Glyph path encoding:
97*c8dee2aaSAndroid Build Coastguard Worker     //
98*c8dee2aaSAndroid Build Coastguard Worker     //   "data": {
99*c8dee2aaSAndroid Build Coastguard Worker     //       "shapes": [                         // follows the shape layer format
100*c8dee2aaSAndroid Build Coastguard Worker     //           {
101*c8dee2aaSAndroid Build Coastguard Worker     //               "ty": "gr",                 // group shape type
102*c8dee2aaSAndroid Build Coastguard Worker     //               "it": [                     // group items
103*c8dee2aaSAndroid Build Coastguard Worker     //                   {
104*c8dee2aaSAndroid Build Coastguard Worker     //                       "ty": "sh",         // actual shape
105*c8dee2aaSAndroid Build Coastguard Worker     //                       "ks": <path data>   // animatable path format, but always static
106*c8dee2aaSAndroid Build Coastguard Worker     //                   },
107*c8dee2aaSAndroid Build Coastguard Worker     //                   ...
108*c8dee2aaSAndroid Build Coastguard Worker     //               ]
109*c8dee2aaSAndroid Build Coastguard Worker     //           },
110*c8dee2aaSAndroid Build Coastguard Worker     //           ...
111*c8dee2aaSAndroid Build Coastguard Worker     //       ]
112*c8dee2aaSAndroid Build Coastguard Worker     //   }
113*c8dee2aaSAndroid Build Coastguard Worker 
114*c8dee2aaSAndroid Build Coastguard Worker     const skjson::ArrayValue* jshapes = jdata["shapes"];
115*c8dee2aaSAndroid Build Coastguard Worker     if (!jshapes) {
116*c8dee2aaSAndroid Build Coastguard Worker         // Space/empty glyph.
117*c8dee2aaSAndroid Build Coastguard Worker         return true;
118*c8dee2aaSAndroid Build Coastguard Worker     }
119*c8dee2aaSAndroid Build Coastguard Worker 
120*c8dee2aaSAndroid Build Coastguard Worker     for (const skjson::ObjectValue* jgrp : *jshapes) {
121*c8dee2aaSAndroid Build Coastguard Worker         if (!jgrp) {
122*c8dee2aaSAndroid Build Coastguard Worker             return false;
123*c8dee2aaSAndroid Build Coastguard Worker         }
124*c8dee2aaSAndroid Build Coastguard Worker 
125*c8dee2aaSAndroid Build Coastguard Worker         const skjson::ArrayValue* jit = (*jgrp)["it"];
126*c8dee2aaSAndroid Build Coastguard Worker         if (!jit) {
127*c8dee2aaSAndroid Build Coastguard Worker             return false;
128*c8dee2aaSAndroid Build Coastguard Worker         }
129*c8dee2aaSAndroid Build Coastguard Worker 
130*c8dee2aaSAndroid Build Coastguard Worker         for (const skjson::ObjectValue* jshape : *jit) {
131*c8dee2aaSAndroid Build Coastguard Worker             if (!jshape) {
132*c8dee2aaSAndroid Build Coastguard Worker                 return false;
133*c8dee2aaSAndroid Build Coastguard Worker             }
134*c8dee2aaSAndroid Build Coastguard Worker 
135*c8dee2aaSAndroid Build Coastguard Worker             // Glyph paths should never be animated.  But they are encoded as
136*c8dee2aaSAndroid Build Coastguard Worker             // animatable properties, so we use the appropriate helpers.
137*c8dee2aaSAndroid Build Coastguard Worker             skottie::internal::AnimationBuilder::AutoScope ascope(abuilder);
138*c8dee2aaSAndroid Build Coastguard Worker             auto path_node = abuilder->attachPath((*jshape)["ks"]);
139*c8dee2aaSAndroid Build Coastguard Worker             auto animators = ascope.release();
140*c8dee2aaSAndroid Build Coastguard Worker 
141*c8dee2aaSAndroid Build Coastguard Worker             if (!path_node || !animators.empty()) {
142*c8dee2aaSAndroid Build Coastguard Worker                 return false;
143*c8dee2aaSAndroid Build Coastguard Worker             }
144*c8dee2aaSAndroid Build Coastguard Worker 
145*c8dee2aaSAndroid Build Coastguard Worker             path->addPath(path_node->getPath());
146*c8dee2aaSAndroid Build Coastguard Worker         }
147*c8dee2aaSAndroid Build Coastguard Worker     }
148*c8dee2aaSAndroid Build Coastguard Worker 
149*c8dee2aaSAndroid Build Coastguard Worker     return true;
150*c8dee2aaSAndroid Build Coastguard Worker }
151*c8dee2aaSAndroid Build Coastguard Worker 
152*c8dee2aaSAndroid Build Coastguard Worker sk_sp<sksg::RenderNode>
ParseGlyphComp(const AnimationBuilder * abuilder,const skjson::ObjectValue & jdata,SkSize * glyph_size)153*c8dee2aaSAndroid Build Coastguard Worker CustomFont::Builder::ParseGlyphComp(const AnimationBuilder* abuilder,
154*c8dee2aaSAndroid Build Coastguard Worker                                     const skjson::ObjectValue& jdata,
155*c8dee2aaSAndroid Build Coastguard Worker                                     SkSize* glyph_size) {
156*c8dee2aaSAndroid Build Coastguard Worker     // Glyph comp encoding:
157*c8dee2aaSAndroid Build Coastguard Worker     //
158*c8dee2aaSAndroid Build Coastguard Worker     //   "data": {                     // Follows the precomp layer format.
159*c8dee2aaSAndroid Build Coastguard Worker     //       "ip": <in point>,
160*c8dee2aaSAndroid Build Coastguard Worker     //       "op": <out point>,
161*c8dee2aaSAndroid Build Coastguard Worker     //       "refId": <comp ID>,
162*c8dee2aaSAndroid Build Coastguard Worker     //       "sr": <time remap info>,
163*c8dee2aaSAndroid Build Coastguard Worker     //       "st": <time remap info>,
164*c8dee2aaSAndroid Build Coastguard Worker     //       "ks": <transform info>
165*c8dee2aaSAndroid Build Coastguard Worker     //   }
166*c8dee2aaSAndroid Build Coastguard Worker 
167*c8dee2aaSAndroid Build Coastguard Worker     AnimationBuilder::LayerInfo linfo{
168*c8dee2aaSAndroid Build Coastguard Worker         {0,0},
169*c8dee2aaSAndroid Build Coastguard Worker         ParseDefault<float>(jdata["ip"], 0.0f),
170*c8dee2aaSAndroid Build Coastguard Worker         ParseDefault<float>(jdata["op"], 0.0f)
171*c8dee2aaSAndroid Build Coastguard Worker     };
172*c8dee2aaSAndroid Build Coastguard Worker 
173*c8dee2aaSAndroid Build Coastguard Worker     if (!linfo.fInPoint && !linfo.fOutPoint) {
174*c8dee2aaSAndroid Build Coastguard Worker         // Not a comp glyph.
175*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
176*c8dee2aaSAndroid Build Coastguard Worker     }
177*c8dee2aaSAndroid Build Coastguard Worker 
178*c8dee2aaSAndroid Build Coastguard Worker     // Since the glyph composition encoding matches the precomp layer encoding, we can pretend
179*c8dee2aaSAndroid Build Coastguard Worker     // we're attaching a precomp here.
180*c8dee2aaSAndroid Build Coastguard Worker     auto comp_node = abuilder->attachPrecompLayer(jdata, &linfo);
181*c8dee2aaSAndroid Build Coastguard Worker 
182*c8dee2aaSAndroid Build Coastguard Worker     // Normalize for 1pt.
183*c8dee2aaSAndroid Build Coastguard Worker     static constexpr float kPtScale = 0.01f;
184*c8dee2aaSAndroid Build Coastguard Worker 
185*c8dee2aaSAndroid Build Coastguard Worker     // For bounds/alignment purposes, we use a glyph size matching the normalized glyph comp size.
186*c8dee2aaSAndroid Build Coastguard Worker     *glyph_size = {linfo.fSize.fWidth * kPtScale, linfo.fSize.fHeight * kPtScale};
187*c8dee2aaSAndroid Build Coastguard Worker 
188*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<sksg::Transform> glyph_transform =
189*c8dee2aaSAndroid Build Coastguard Worker             sksg::Matrix<SkMatrix>::Make(SkMatrix::Scale(kPtScale, kPtScale));
190*c8dee2aaSAndroid Build Coastguard Worker 
191*c8dee2aaSAndroid Build Coastguard Worker     // Additional/explicit glyph transform (not handled in attachPrecompLayer).
192*c8dee2aaSAndroid Build Coastguard Worker     if (const skjson::ObjectValue* jtransform = jdata["ks"]) {
193*c8dee2aaSAndroid Build Coastguard Worker         glyph_transform = abuilder->attachMatrix2D(*jtransform, std::move(glyph_transform));
194*c8dee2aaSAndroid Build Coastguard Worker     }
195*c8dee2aaSAndroid Build Coastguard Worker 
196*c8dee2aaSAndroid Build Coastguard Worker     return sksg::TransformEffect::Make(abuilder->attachPrecompLayer(jdata, &linfo),
197*c8dee2aaSAndroid Build Coastguard Worker                                        std::move(glyph_transform));
198*c8dee2aaSAndroid Build Coastguard Worker }
199*c8dee2aaSAndroid Build Coastguard Worker 
detach()200*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<CustomFont> CustomFont::Builder::detach() {
201*c8dee2aaSAndroid Build Coastguard Worker     return std::unique_ptr<CustomFont>(new CustomFont(std::move(fGlyphComps),
202*c8dee2aaSAndroid Build Coastguard Worker                                                       fCustomBuilder.detach()));
203*c8dee2aaSAndroid Build Coastguard Worker }
204*c8dee2aaSAndroid Build Coastguard Worker 
CustomFont(GlyphCompMap && glyph_comps,sk_sp<SkTypeface> tf)205*c8dee2aaSAndroid Build Coastguard Worker CustomFont::CustomFont(GlyphCompMap&& glyph_comps, sk_sp<SkTypeface> tf)
206*c8dee2aaSAndroid Build Coastguard Worker     : fGlyphComps(std::move(glyph_comps))
207*c8dee2aaSAndroid Build Coastguard Worker     , fTypeface(std::move(tf))
208*c8dee2aaSAndroid Build Coastguard Worker {}
209*c8dee2aaSAndroid Build Coastguard Worker 
210*c8dee2aaSAndroid Build Coastguard Worker CustomFont::~CustomFont() = default;
211*c8dee2aaSAndroid Build Coastguard Worker 
getGlyphComp(const SkTypeface * tf,SkGlyphID gid) const212*c8dee2aaSAndroid Build Coastguard Worker sk_sp<sksg::RenderNode> CustomFont::GlyphCompMapper::getGlyphComp(const SkTypeface* tf,
213*c8dee2aaSAndroid Build Coastguard Worker                                                                   SkGlyphID gid) const {
214*c8dee2aaSAndroid Build Coastguard Worker     for (const auto& font : fFonts) {
215*c8dee2aaSAndroid Build Coastguard Worker         if (font->typeface().get() == tf) {
216*c8dee2aaSAndroid Build Coastguard Worker             auto* comp_node = font->fGlyphComps.find(gid);
217*c8dee2aaSAndroid Build Coastguard Worker             return comp_node ? *comp_node : nullptr;
218*c8dee2aaSAndroid Build Coastguard Worker         }
219*c8dee2aaSAndroid Build Coastguard Worker     }
220*c8dee2aaSAndroid Build Coastguard Worker 
221*c8dee2aaSAndroid Build Coastguard Worker     return nullptr;
222*c8dee2aaSAndroid Build Coastguard Worker }
223*c8dee2aaSAndroid Build Coastguard Worker 
224*c8dee2aaSAndroid Build Coastguard Worker }  // namespace skottie::internal
225