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