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