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