Flutter Engine
The Flutter Engine
PropertyObserver.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2023 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
14#include "tests/Test.h"
15#include "tools/ToolUtils.h"
17
18using namespace skottie;
19
20namespace {
21
22TextPropertyValue make_text_prop(const char* str) {
24
26 prop.fText = SkString(str);
27
28 return prop;
29}
30
31class MockPropertyObserver final : public PropertyObserver {
32public:
33 explicit MockPropertyObserver(bool override_props = false) : fOverrideProps(override_props) {}
34
35 struct ColorInfo {
36 SkString node_name;
37 std::unique_ptr<skottie::ColorPropertyHandle> handle;
38 };
39
40 struct OpacityInfo {
41 SkString node_name;
42 std::unique_ptr<skottie::OpacityPropertyHandle> handle;
43 };
44
45 struct TextInfo {
46 SkString node_name;
47 std::unique_ptr<skottie::TextPropertyHandle> handle;
48 };
49
50 struct TransformInfo {
51 SkString node_name;
52 std::unique_ptr<skottie::TransformPropertyHandle> handle;
53 };
54
55 void onColorProperty(const char node_name[],
57 auto prop_handle = lh();
58 if (fOverrideProps) {
59 static constexpr ColorPropertyValue kOverrideColor = 0xffffff00;
60 prop_handle->set(kOverrideColor);
61 }
62 fColors.push_back({SkString(node_name), std::move(prop_handle)});
63 fColorsWithFullKeypath.push_back({SkString(fCurrentNode.c_str()), lh()});
64 }
65
66 void onOpacityProperty(const char node_name[],
68 auto prop_handle = lh();
69 if (fOverrideProps) {
70 static constexpr OpacityPropertyValue kOverrideOpacity = 0.75f;
71 prop_handle->set(kOverrideOpacity);
72 }
73 fOpacities.push_back({SkString(node_name), std::move(prop_handle)});
74 }
75
76 void onTextProperty(const char node_name[],
78 auto prop_handle = lh();
79 if (fOverrideProps) {
80 static const TextPropertyValue kOverrideText = make_text_prop("foo");
81 prop_handle->set(kOverrideText);
82 }
83 fTexts.push_back({SkString(node_name), std::move(prop_handle)});
84 }
85
86 void onTransformProperty(const char node_name[],
88 auto prop_handle = lh();
89 if (fOverrideProps) {
90 static constexpr TransformPropertyValue kOverrideTransform = {
91 { 100, 100 },
92 { 200, 200 },
93 { 2, 2 },
94 45,
95 0,
96 0,
97 };
98 prop_handle->set(kOverrideTransform);
99 }
100 fTransforms.push_back({SkString(node_name), std::move(prop_handle)});
101 }
102
103 void onEnterNode(const char node_name[], PropertyObserver::NodeType node_type) override {
104 if (node_name == nullptr) {
105 return;
106 }
107 fCurrentNode = fCurrentNode.empty() ? node_name : fCurrentNode + "." + node_name;
108 }
109
110 void onLeavingNode(const char node_name[], PropertyObserver::NodeType node_type) override {
111 if (node_name == nullptr) {
112 return;
113 }
114 auto length = strlen(node_name);
115 fCurrentNode =
116 fCurrentNode.length() > length
117 ? fCurrentNode.substr(0, fCurrentNode.length() - strlen(node_name) - 1)
118 : "";
119 }
120
121 const std::vector<ColorInfo>& colors() const { return fColors; }
122 const std::vector<OpacityInfo>& opacities() const { return fOpacities; }
123 const std::vector<TextInfo>& texts() const { return fTexts; }
124 const std::vector<TransformInfo>& transforms() const { return fTransforms; }
125 const std::vector<ColorInfo>& colorsWithFullKeypath() const {
126 return fColorsWithFullKeypath;
127 }
128
129private:
130 const bool fOverrideProps;
131 std::vector<ColorInfo> fColors;
132 std::vector<OpacityInfo> fOpacities;
133 std::vector<TextInfo> fTexts;
134 std::vector<TransformInfo> fTransforms;
135 std::string fCurrentNode;
136 std::vector<ColorInfo> fColorsWithFullKeypath;
137};
138
139// Returns a single specified typeface for all requests.
140class MockFontMgr : public SkFontMgr {
141 public:
142 MockFontMgr(sk_sp<SkTypeface> test_font) : fTestFont(std::move(test_font)) {}
143
144 int onCountFamilies() const override { return 1; }
145 void onGetFamilyName(int index, SkString* familyName) const override {}
146 sk_sp<SkFontStyleSet> onCreateStyleSet(int index) const override { return nullptr; }
147 sk_sp<SkFontStyleSet> onMatchFamily(const char familyName[]) const override {
148 return nullptr;
149 }
150 sk_sp<SkTypeface> onMatchFamilyStyle(const char familyName[],
151 const SkFontStyle& fontStyle) const override {
152 return nullptr;
153 }
154 sk_sp<SkTypeface> onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&,
155 const char* bcp47[], int bcp47Count,
156 SkUnichar character) const override {
157 return nullptr;
158 }
159 sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override {
160 return fTestFont;
161 }
162 sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
163 int ttcIndex) const override {
164 return fTestFont;
165 }
166 sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
167 const SkFontArguments&) const override {
168 return fTestFont;
169 }
170 sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
171 return fTestFont;
172 }
173 sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[], SkFontStyle) const override {
174 return fTestFont;
175 }
176 private:
177 sk_sp<SkTypeface> fTestFont;
178};
179
180static const char gTestJson[] = R"({
181 "v": "5.2.1",
182 "w": 100,
183 "h": 100,
184 "fr": 1,
185 "ip": 0,
186 "op": 1,
187 "fonts": {
188 "list": [
189 {
190 "fName": "test_font",
191 "fFamily": "test-family",
192 "fStyle": "TestFontStyle"
193 }
194 ]
195 },
196 "layers": [
197 {
198 "ty": 4,
199 "nm": "layer_0",
200 "ind": 0,
201 "ip": 0,
202 "op": 1,
203 "ks": {
204 "o": { "a": 0, "k": 50 }
205 },
206 "ef": [{
207 "ef": [
208 {},
209 {},
210 { "v": { "a": 0, "k": [ 0, 1, 0 ] }},
211 {},
212 {},
213 {},
214 { "v": { "a": 0, "k": 1 }}
215 ],
216 "nm": "fill_effect_0",
217 "mn": "ADBE Fill",
218 "ty": 21
219 }],
220 "shapes": [
221 {
222 "ty": "el",
223 "nm": "geometry_0",
224 "p": { "a": 0, "k": [ 50, 50 ] },
225 "s": { "a": 0, "k": [ 50, 50 ] }
226 },
227 {
228 "ty": "fl",
229 "nm": "fill_0",
230 "c": { "a": 0, "k": [ 1, 0, 0] }
231 },
232 {
233 "ty": "tr",
234 "nm": "shape_transform_0",
235 "o": { "a": 0, "k": 100 },
236 "s": { "a": 0, "k": [ 50, 50 ] }
237 }
238 ]
239 },
240 {
241 "ty": 5,
242 "nm": "layer_1",
243 "ip": 0,
244 "op": 1,
245 "ks": {
246 "p": { "a": 0, "k": [25, 25] }
247 },
248 "t": {
249 "d": {
250 "k": [
251 {
252 "t": 0,
253 "s": {
254 "f": "test_font",
255 "s": 100,
256 "t": "inline_text",
257 "lh": 120,
258 "ls": 12
259 }
260 }
261 ]
262 }
263 }
264 }
265 ]
266 })";
267
268} // anonymous namespace
269
270DEF_TEST(Skottie_Props, reporter) {
273 auto test_font_manager = sk_make_sp<MockFontMgr>(test_typeface);
274
275 SkMemoryStream stream(gTestJson, strlen(gTestJson));
276 auto observer = sk_make_sp<MockPropertyObserver>();
277
278 auto animation = skottie::Animation::Builder()
279 .setPropertyObserver(observer)
280 .setFontManager(test_font_manager)
281 .make(&stream);
282
283 REPORTER_ASSERT(reporter, animation);
284
285 const auto& colors = observer->colors();
286 REPORTER_ASSERT(reporter, colors.size() == 2);
287 REPORTER_ASSERT(reporter, colors[0].node_name.equals("fill_0"));
288 REPORTER_ASSERT(reporter, colors[0].handle->get() == 0xffff0000);
289 REPORTER_ASSERT(reporter, colors[1].node_name.equals("fill_effect_0"));
290 REPORTER_ASSERT(reporter, colors[1].handle->get() == 0xff00ff00);
291
292 const auto& colorsWithFullKeypath = observer->colorsWithFullKeypath();
293 REPORTER_ASSERT(reporter, colorsWithFullKeypath.size() == 2);
294 REPORTER_ASSERT(reporter, colorsWithFullKeypath[0].node_name.equals("layer_0.fill_0"));
295 REPORTER_ASSERT(reporter, colorsWithFullKeypath[0].handle->get() == 0xffff0000);
296 REPORTER_ASSERT(reporter, colorsWithFullKeypath[1].node_name.equals("layer_0.fill_effect_0"));
297 REPORTER_ASSERT(reporter, colorsWithFullKeypath[1].handle->get() == 0xff00ff00);
298
299 const auto& opacities = observer->opacities();
300 REPORTER_ASSERT(reporter, opacities.size() == 3);
301 REPORTER_ASSERT(reporter, opacities[0].node_name.equals("shape_transform_0"));
302 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[0].handle->get(), 100));
303 REPORTER_ASSERT(reporter, opacities[1].node_name.equals("layer_0"));
304 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[1].handle->get(), 50));
305
306 const auto& transforms = observer->transforms();
307 REPORTER_ASSERT(reporter, transforms.size() == 3);
308 REPORTER_ASSERT(reporter, transforms[0].node_name.equals("layer_0"));
309 REPORTER_ASSERT(reporter, transforms[0].handle->get() == skottie::TransformPropertyValue({
310 SkPoint::Make(0, 0),
311 SkPoint::Make(0, 0),
312 SkVector::Make(100, 100),
313 0,
314 0,
315 0
316 }));
317 REPORTER_ASSERT(reporter, transforms[1].node_name.equals("layer_1"));
318 REPORTER_ASSERT(reporter, transforms[1].handle->get() == skottie::TransformPropertyValue({
319 SkPoint::Make(0, 0),
320 SkPoint::Make(25, 25),
321 SkVector::Make(100, 100),
322 0,
323 0,
324 0
325 }));
326 REPORTER_ASSERT(reporter, transforms[2].node_name.equals("shape_transform_0"));
327 REPORTER_ASSERT(reporter, transforms[2].handle->get() == skottie::TransformPropertyValue({
328 SkPoint::Make(0, 0),
329 SkPoint::Make(0, 0),
330 SkVector::Make(50, 50),
331 0,
332 0,
333 0
334 }));
335
336 const auto& texts = observer->texts();
337 REPORTER_ASSERT(reporter, texts.size() == 1);
338 REPORTER_ASSERT(reporter, texts[0].node_name.equals("layer_1"));
341 SkString("inline_text"),
342 100,
343 0, 100,
344 0,
345 120,
346 12,
347 0,
348 0,
350 Shaper::VAlign::kTopBaseline,
352 Shaper::LinebreakPolicy::kExplicit,
353 Shaper::Direction::kLTR,
358 TextPaintOrder::kFillStroke,
359 SkPaint::Join::kDefault_Join,
360 false,
361 false,
362 nullptr,
363 SkString(),
364 SkString("test-family")
365 });
366 REPORTER_ASSERT(reporter, texts[0].handle->get() == text_prop);
367 text_prop.fLocale = "custom_lc";
368 texts[0].handle->set(text_prop);
369 REPORTER_ASSERT(reporter, texts[0].handle->get() == text_prop);
370}
371
372DEF_TEST(Skottie_Props_Revalidation, reporter) {
375 auto test_font_manager = sk_make_sp<MockFontMgr>(test_typeface);
376
377 SkMemoryStream stream(gTestJson, strlen(gTestJson));
378 auto observer = sk_make_sp<MockPropertyObserver>(true);
379
380 auto animation = skottie::Animation::Builder()
381 .setPropertyObserver(observer)
382 .setFontManager(test_font_manager)
383 .make(&stream);
384
385 REPORTER_ASSERT(reporter, animation);
386
387 SkPictureRecorder recorder;
388 // Rendering without seek() should not trigger revalidation asserts.
389 animation->render(recorder.beginRecording(100, 100));
390
391 // Mutate all props.
392 for (const auto& c : observer->colors()) {
393 c.handle->set(SK_ColorGRAY);
394 }
395 for (const auto& o : observer->opacities()) {
396 o.handle->set(0.25f);
397 }
398 for (const auto& t : observer->texts()) {
399 t.handle->set(make_text_prop("bar"));
400 }
401 for (const auto& t : observer->transforms()) {
402 t.handle->set({
403 { 1000, 1000 },
404 { 2000, 2000 },
405 { 20, 20 },
406 5,
407 0,
408 0,
409 });
410 }
411
412 // Rendering without seek() after property mutation should not trigger revalidation asserts.
413 animation->render(recorder.beginRecording(100, 100));
414}
415
reporter
Definition: FontMgrTest.cpp:39
static void test_typeface(skiatest::Reporter *reporter)
DEF_TEST(Skottie_Props, reporter)
constexpr SkColor SK_ColorTRANSPARENT
Definition: SkColor.h:99
constexpr SkColor SK_ColorGRAY
Definition: SkColor.h:113
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: SkScalar.h:107
int32_t SkUnichar
Definition: SkTypes.h:175
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
virtual void onGetFamilyName(int index, SkString *familyName) const =0
virtual sk_sp< SkTypeface > onMakeFromData(sk_sp< SkData >, int ttcIndex) const =0
virtual sk_sp< SkFontStyleSet > onCreateStyleSet(int index) const =0
virtual sk_sp< SkTypeface > onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle &, const char *bcp47[], int bcp47Count, SkUnichar character) const =0
virtual sk_sp< SkFontStyleSet > onMatchFamily(const char familyName[]) const =0
virtual sk_sp< SkTypeface > onMakeFromStreamIndex(std::unique_ptr< SkStreamAsset >, int ttcIndex) const =0
virtual sk_sp< SkTypeface > onMakeFromStreamArgs(std::unique_ptr< SkStreamAsset >, const SkFontArguments &) const =0
virtual sk_sp< SkTypeface > onMatchFamilyStyle(const char familyName[], const SkFontStyle &) const =0
virtual sk_sp< SkTypeface > onLegacyMakeTypeface(const char familyName[], SkFontStyle) const =0
virtual sk_sp< SkTypeface > onMakeFromFile(const char path[], int ttcIndex) const =0
virtual int onCountFamilies() const =0
SkCanvas * beginRecording(const SkRect &bounds, sk_sp< SkBBoxHierarchy > bbh)
virtual void onTransformProperty(const char node_name[], const LazyHandle< TransformPropertyHandle > &)
virtual void onLeavingNode(const char node_name[], NodeType node_type)
virtual void onOpacityProperty(const char node_name[], const LazyHandle< OpacityPropertyHandle > &)
virtual void onTextProperty(const char node_name[], const LazyHandle< TextPropertyHandle > &)
virtual void onEnterNode(const char node_name[], NodeType node_type)
std::function< std::unique_ptr< T >()> LazyHandle
virtual void onColorProperty(const char node_name[], const LazyHandle< ColorPropertyHandle > &)
size_t length
PODArray< SkColor > colors
Definition: SkRecords.h:276
sk_sp< SkTypeface > DefaultPortableTypeface()
DlVertices::Builder Builder
@ kNone
Definition: layer.h:53
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
float OpacityPropertyValue
SkColor ColorPropertyValue
Definition: ref_ptr.h:256
static SkScalar prop(SkScalar radius, SkScalar newSize, SkScalar oldSize)
Definition: rrect.cpp:83
static constexpr SkRect MakeEmpty()
Definition: SkRect.h:595