1 /*
2 * Copyright 2018 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 "include/core/SkFontMgr.h"
9 #include "include/core/SkMatrix.h"
10 #include "include/core/SkStream.h"
11 #include "include/core/SkTextBlob.h"
12 #include "include/core/SkTypeface.h"
13 #include "modules/skottie/include/Skottie.h"
14 #include "modules/skottie/include/SkottieProperty.h"
15 #include "modules/skottie/src/text/SkottieShaper.h"
16 #include "src/core/SkFontDescriptor.h"
17 #include "src/core/SkTextBlobPriv.h"
18 #include "tests/Test.h"
19 #include "tools/ToolUtils.h"
20
21 #include <cmath>
22 #include <string>
23 #include <tuple>
24 #include <vector>
25
26 using namespace skottie;
27
DEF_TEST(Skottie_OssFuzz8956,reporter)28 DEF_TEST(Skottie_OssFuzz8956, reporter) {
29 static constexpr char json[] =
30 "{\"v\":\" \",\"fr\":3,\"w\":4,\"h\":3,\"layers\":[{\"ty\": 1, \"sw\": 10, \"sh\": 10,"
31 " \"sc\":\"#ffffff\", \"ks\":{\"o\":{\"a\": true, \"k\":"
32 " [{\"t\": 0, \"s\": 0, \"e\": 1, \"i\": {\"x\":[]}}]}}}]}";
33
34 SkMemoryStream stream(json, strlen(json));
35
36 // Passes if parsing doesn't crash.
37 auto animation = Animation::Make(&stream);
38 }
39
DEF_TEST(Skottie_Properties,reporter)40 DEF_TEST(Skottie_Properties, reporter) {
41 auto test_typeface = ToolUtils::create_portable_typeface();
42 REPORTER_ASSERT(reporter, test_typeface);
43
44 static const char json[] = R"({
45 "v": "5.2.1",
46 "w": 100,
47 "h": 100,
48 "fr": 1,
49 "ip": 0,
50 "op": 1,
51 "fonts": {
52 "list": [
53 {
54 "fName": "test_font",
55 "fFamily": "test-family",
56 "fStyle": "TestFontStyle"
57 }
58 ]
59 },
60 "layers": [
61 {
62 "ty": 4,
63 "nm": "layer_0",
64 "ind": 0,
65 "ip": 0,
66 "op": 1,
67 "ks": {
68 "o": { "a": 0, "k": 50 }
69 },
70 "ef": [{
71 "ef": [
72 {},
73 {},
74 { "v": { "a": 0, "k": [ 0, 1, 0 ] }},
75 {},
76 {},
77 {},
78 { "v": { "a": 0, "k": 1 }}
79 ],
80 "nm": "fill_effect_0",
81 "mn": "ADBE Fill",
82 "ty": 21
83 }],
84 "shapes": [
85 {
86 "ty": "el",
87 "nm": "geometry_0",
88 "p": { "a": 0, "k": [ 50, 50 ] },
89 "s": { "a": 0, "k": [ 50, 50 ] }
90 },
91 {
92 "ty": "fl",
93 "nm": "fill_0",
94 "c": { "a": 0, "k": [ 1, 0, 0] }
95 },
96 {
97 "ty": "tr",
98 "nm": "shape_transform_0",
99 "o": { "a": 0, "k": 100 },
100 "s": { "a": 0, "k": [ 50, 50 ] }
101 }
102 ]
103 },
104 {
105 "ty": 5,
106 "nm": "layer_1",
107 "ip": 0,
108 "op": 1,
109 "ks": {
110 "p": { "a": 0, "k": [25, 25] }
111 },
112 "t": {
113 "d": {
114 "k": [
115 {
116 "t": 0,
117 "s": {
118 "f": "test_font",
119 "s": 100,
120 "t": "inline_text",
121 "lh": 120,
122 "ls": 12
123 }
124 }
125 ]
126 }
127 }
128 }
129 ]
130 })";
131
132
133 class TestPropertyObserver final : public PropertyObserver {
134 public:
135 struct ColorInfo {
136 SkString node_name;
137 std::unique_ptr<skottie::ColorPropertyHandle> handle;
138 };
139
140 struct OpacityInfo {
141 SkString node_name;
142 std::unique_ptr<skottie::OpacityPropertyHandle> handle;
143 };
144
145 struct TextInfo {
146 SkString node_name;
147 std::unique_ptr<skottie::TextPropertyHandle> handle;
148 };
149
150 struct TransformInfo {
151 SkString node_name;
152 std::unique_ptr<skottie::TransformPropertyHandle> handle;
153 };
154
155 void onColorProperty(const char node_name[],
156 const PropertyObserver::LazyHandle<ColorPropertyHandle>& lh) override {
157 fColors.push_back({SkString(node_name), lh()});
158 fColorsWithFullKeypath.push_back({SkString(fCurrentNode.c_str()), lh()});
159 }
160
161 void onOpacityProperty(const char node_name[],
162 const PropertyObserver::LazyHandle<OpacityPropertyHandle>& lh) override {
163 fOpacities.push_back({SkString(node_name), lh()});
164 }
165
166 void onTextProperty(const char node_name[],
167 const PropertyObserver::LazyHandle<TextPropertyHandle>& lh) override {
168 fTexts.push_back({SkString(node_name), lh()});
169 }
170
171 void onTransformProperty(const char node_name[],
172 const PropertyObserver::LazyHandle<TransformPropertyHandle>& lh) override {
173 fTransforms.push_back({SkString(node_name), lh()});
174 }
175
176 void onEnterNode(const char node_name[], PropertyObserver::NodeType node_type) override {
177 if (node_name == nullptr) {
178 return;
179 }
180 fCurrentNode = fCurrentNode.empty() ? node_name : fCurrentNode + "." + node_name;
181 }
182
183 void onLeavingNode(const char node_name[], PropertyObserver::NodeType node_type) override {
184 if (node_name == nullptr) {
185 return;
186 }
187 auto length = strlen(node_name);
188 fCurrentNode =
189 fCurrentNode.length() > length
190 ? fCurrentNode.substr(0, fCurrentNode.length() - strlen(node_name) - 1)
191 : "";
192 }
193
194 const std::vector<ColorInfo>& colors() const { return fColors; }
195 const std::vector<OpacityInfo>& opacities() const { return fOpacities; }
196 const std::vector<TextInfo>& texts() const { return fTexts; }
197 const std::vector<TransformInfo>& transforms() const { return fTransforms; }
198 const std::vector<ColorInfo>& colorsWithFullKeypath() const {
199 return fColorsWithFullKeypath;
200 }
201
202 private:
203 std::vector<ColorInfo> fColors;
204 std::vector<OpacityInfo> fOpacities;
205 std::vector<TextInfo> fTexts;
206 std::vector<TransformInfo> fTransforms;
207 std::string fCurrentNode;
208 std::vector<ColorInfo> fColorsWithFullKeypath;
209 };
210
211 // Returns a single specified typeface for all requests.
212 class FakeFontMgr : public SkFontMgr {
213 public:
214 FakeFontMgr(sk_sp<SkTypeface> test_font) : fTestFont(test_font) {}
215
216 int onCountFamilies() const override { return 1; }
217 void onGetFamilyName(int index, SkString* familyName) const override {}
218 SkFontStyleSet* onCreateStyleSet(int index) const override { return nullptr; }
219 SkFontStyleSet* onMatchFamily(const char familyName[]) const override { return nullptr; }
220 SkTypeface* onMatchFamilyStyle(const char familyName[],
221 const SkFontStyle& fontStyle) const override {
222 return nullptr;
223 }
224 SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&,
225 const char* bcp47[], int bcp47Count,
226 SkUnichar character) const override {
227 return nullptr;
228 }
229 sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override {
230 return fTestFont;
231 }
232 sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
233 int ttcIndex) const override {
234 return fTestFont;
235 }
236 sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
237 const SkFontArguments&) const override {
238 return fTestFont;
239 }
240 sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
241 return fTestFont;
242 }
243 sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[], SkFontStyle) const override {
244 return fTestFont;
245 }
246 private:
247 sk_sp<SkTypeface> fTestFont;
248 };
249
250 sk_sp<FakeFontMgr> test_font_manager = sk_make_sp<FakeFontMgr>(test_typeface);
251 SkMemoryStream stream(json, strlen(json));
252 auto observer = sk_make_sp<TestPropertyObserver>();
253
254 auto animation = skottie::Animation::Builder()
255 .setPropertyObserver(observer)
256 .setFontManager(test_font_manager)
257 .make(&stream);
258
259 REPORTER_ASSERT(reporter, animation);
260
261 const auto& colors = observer->colors();
262 REPORTER_ASSERT(reporter, colors.size() == 2);
263 REPORTER_ASSERT(reporter, colors[0].node_name.equals("fill_0"));
264 REPORTER_ASSERT(reporter, colors[0].handle->get() == 0xffff0000);
265 REPORTER_ASSERT(reporter, colors[1].node_name.equals("fill_effect_0"));
266 REPORTER_ASSERT(reporter, colors[1].handle->get() == 0xff00ff00);
267
268 const auto& colorsWithFullKeypath = observer->colorsWithFullKeypath();
269 REPORTER_ASSERT(reporter, colorsWithFullKeypath.size() == 2);
270 REPORTER_ASSERT(reporter, colorsWithFullKeypath[0].node_name.equals("layer_0.fill_0"));
271 REPORTER_ASSERT(reporter, colorsWithFullKeypath[0].handle->get() == 0xffff0000);
272 REPORTER_ASSERT(reporter, colorsWithFullKeypath[1].node_name.equals("layer_0.fill_effect_0"));
273 REPORTER_ASSERT(reporter, colorsWithFullKeypath[1].handle->get() == 0xff00ff00);
274
275 const auto& opacities = observer->opacities();
276 REPORTER_ASSERT(reporter, opacities.size() == 3);
277 REPORTER_ASSERT(reporter, opacities[0].node_name.equals("shape_transform_0"));
278 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[0].handle->get(), 100));
279 REPORTER_ASSERT(reporter, opacities[1].node_name.equals("layer_0"));
280 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[1].handle->get(), 50));
281
282 const auto& transforms = observer->transforms();
283 REPORTER_ASSERT(reporter, transforms.size() == 3);
284 REPORTER_ASSERT(reporter, transforms[0].node_name.equals("layer_0"));
285 REPORTER_ASSERT(reporter, transforms[0].handle->get() == skottie::TransformPropertyValue({
286 SkPoint::Make(0, 0),
287 SkPoint::Make(0, 0),
288 SkVector::Make(100, 100),
289 0,
290 0,
291 0
292 }));
293 REPORTER_ASSERT(reporter, transforms[1].node_name.equals("layer_1"));
294 REPORTER_ASSERT(reporter, transforms[1].handle->get() == skottie::TransformPropertyValue({
295 SkPoint::Make(0, 0),
296 SkPoint::Make(25, 25),
297 SkVector::Make(100, 100),
298 0,
299 0,
300 0
301 }));
302 REPORTER_ASSERT(reporter, transforms[2].node_name.equals("shape_transform_0"));
303 REPORTER_ASSERT(reporter, transforms[2].handle->get() == skottie::TransformPropertyValue({
304 SkPoint::Make(0, 0),
305 SkPoint::Make(0, 0),
306 SkVector::Make(50, 50),
307 0,
308 0,
309 0
310 }));
311
312 const auto& texts = observer->texts();
313 REPORTER_ASSERT(reporter, texts.size() == 1);
314 REPORTER_ASSERT(reporter, texts[0].node_name.equals("layer_1"));
315 REPORTER_ASSERT(reporter, texts[0].handle->get() == skottie::TextPropertyValue({
316 test_typeface,
317 SkString("inline_text"),
318 100,
319 0, 100,
320 0,
321 120,
322 12,
323 0,
324 SkTextUtils::kLeft_Align,
325 Shaper::VAlign::kTopBaseline,
326 Shaper::ResizePolicy::kNone,
327 Shaper::LinebreakPolicy::kExplicit,
328 Shaper::Direction::kLTR,
329 Shaper::Capitalization::kNone,
330 SkRect::MakeEmpty(),
331 SK_ColorTRANSPARENT,
332 SK_ColorTRANSPARENT,
333 TextPaintOrder::kFillStroke,
334 false,
335 false
336 }));
337 }
338
DEF_TEST(Skottie_Annotations,reporter)339 DEF_TEST(Skottie_Annotations, reporter) {
340 static constexpr char json[] = R"({
341 "v": "5.2.1",
342 "w": 100,
343 "h": 100,
344 "fr": 10,
345 "ip": 0,
346 "op": 100,
347 "layers": [
348 {
349 "ty": 1,
350 "ind": 0,
351 "ip": 0,
352 "op": 1,
353 "ks": {
354 "o": { "a": 0, "k": 50 }
355 },
356 "sw": 100,
357 "sh": 100,
358 "sc": "#ffffff"
359 }
360 ],
361 "markers": [
362 {
363 "cm": "marker_1",
364 "dr": 25,
365 "tm": 25
366 },
367 {
368 "cm": "marker_2",
369 "dr": 0,
370 "tm": 75
371 }
372 ]
373 })";
374
375 class TestMarkerObserver final : public MarkerObserver {
376 public:
377 void onMarker(const char name[], float t0, float t1) override {
378 fMarkers.push_back(std::make_tuple(name, t0, t1));
379 }
380
381 std::vector<std::tuple<std::string, float, float>> fMarkers;
382 };
383
384 SkMemoryStream stream(json, strlen(json));
385 auto observer = sk_make_sp<TestMarkerObserver>();
386
387 auto animation = skottie::Animation::Builder()
388 .setMarkerObserver(observer)
389 .make(&stream);
390
391 REPORTER_ASSERT(reporter, animation);
392 REPORTER_ASSERT(reporter, animation->duration() == 10);
393 REPORTER_ASSERT(reporter, animation->inPoint() == 0.0);
394 REPORTER_ASSERT(reporter, animation->outPoint() == 100.0);
395
396 REPORTER_ASSERT(reporter, observer->fMarkers.size() == 2ul);
397 REPORTER_ASSERT(reporter, std::get<0>(observer->fMarkers[0]) == "marker_1");
398 REPORTER_ASSERT(reporter, std::get<1>(observer->fMarkers[0]) == 0.25f);
399 REPORTER_ASSERT(reporter, std::get<2>(observer->fMarkers[0]) == 0.50f);
400 REPORTER_ASSERT(reporter, std::get<0>(observer->fMarkers[1]) == "marker_2");
401 REPORTER_ASSERT(reporter, std::get<1>(observer->fMarkers[1]) == 0.75f);
402 REPORTER_ASSERT(reporter, std::get<2>(observer->fMarkers[1]) == 0.75f);
403 }
404
ComputeBlobBounds(const sk_sp<SkTextBlob> & blob)405 static SkRect ComputeBlobBounds(const sk_sp<SkTextBlob>& blob) {
406 auto bounds = SkRect::MakeEmpty();
407
408 if (!blob) {
409 return bounds;
410 }
411
412 SkAutoSTArray<16, SkRect> glyphBounds;
413
414 for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
415 glyphBounds.reset(SkToInt(it.glyphCount()));
416 it.font().getBounds(it.glyphs(), it.glyphCount(), glyphBounds.get(), nullptr);
417
418 SkASSERT(it.positioning() == SkTextBlobRunIterator::kFull_Positioning);
419 for (uint32_t i = 0; i < it.glyphCount(); ++i) {
420 bounds.join(glyphBounds[i].makeOffset(it.pos()[i * 2 ],
421 it.pos()[i * 2 + 1]));
422 }
423 }
424
425 return bounds;
426 }
427
ComputeShapeResultBounds(const skottie::Shaper::Result & res)428 static SkRect ComputeShapeResultBounds(const skottie::Shaper::Result& res) {
429 auto bounds = SkRect::MakeEmpty();
430
431 for (const auto& fragment : res.fFragments) {
432 bounds.join(ComputeBlobBounds(fragment.fBlob).makeOffset(fragment.fPos.x(),
433 fragment.fPos.y()));
434 }
435
436 return bounds;
437 }
438
DEF_TEST(Skottie_Shaper_HAlign,reporter)439 DEF_TEST(Skottie_Shaper_HAlign, reporter) {
440 auto typeface = SkTypeface::MakeDefault();
441 REPORTER_ASSERT(reporter, typeface);
442
443 static constexpr struct {
444 SkScalar text_size,
445 tolerance;
446 } kTestSizes[] = {
447 // These gross tolerances are required for the test to pass on NativeFonts bots.
448 // Might be worth investigating why we need so much slack.
449 { 5, 2.0f },
450 { 10, 2.0f },
451 { 15, 2.4f },
452 { 25, 4.4f },
453 };
454
455 static constexpr struct {
456 SkTextUtils::Align align;
457 SkScalar l_selector,
458 r_selector;
459 } kTestAligns[] = {
460 { SkTextUtils:: kLeft_Align, 0.0f, 1.0f },
461 { SkTextUtils::kCenter_Align, 0.5f, 0.5f },
462 { SkTextUtils:: kRight_Align, 1.0f, 0.0f },
463 };
464
465 const SkString text("Foo, bar.\rBaz.");
466 const SkPoint text_point = SkPoint::Make(100, 100);
467
468 for (const auto& tsize : kTestSizes) {
469 for (const auto& talign : kTestAligns) {
470 const skottie::Shaper::TextDesc desc = {
471 typeface,
472 tsize.text_size,
473 0, tsize.text_size,
474 tsize.text_size,
475 0,
476 0,
477 talign.align,
478 Shaper::VAlign::kTopBaseline,
479 Shaper::ResizePolicy::kNone,
480 Shaper::LinebreakPolicy::kExplicit,
481 Shaper::Direction::kLTR,
482 Shaper::Capitalization::kNone,
483 Shaper::Flags::kNone
484 };
485
486 const auto shape_result = Shaper::Shape(text, desc, text_point,
487 SkFontMgr::RefDefault());
488 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
489 REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
490
491 const auto shape_bounds = ComputeShapeResultBounds(shape_result);
492 REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
493
494 const auto expected_l = text_point.x() - shape_bounds.width() * talign.l_selector;
495 REPORTER_ASSERT(reporter,
496 std::fabs(shape_bounds.left() - expected_l) < tsize.tolerance,
497 "%f %f %f %f %d", shape_bounds.left(), expected_l, tsize.tolerance,
498 tsize.text_size, talign.align);
499
500 const auto expected_r = text_point.x() + shape_bounds.width() * talign.r_selector;
501 REPORTER_ASSERT(reporter,
502 std::fabs(shape_bounds.right() - expected_r) < tsize.tolerance,
503 "%f %f %f %f %d", shape_bounds.right(), expected_r, tsize.tolerance,
504 tsize.text_size, talign.align);
505
506 }
507 }
508 }
509
DEF_TEST(Skottie_Shaper_VAlign,reporter)510 DEF_TEST(Skottie_Shaper_VAlign, reporter) {
511 auto typeface = SkTypeface::MakeDefault();
512 REPORTER_ASSERT(reporter, typeface);
513
514 static constexpr struct {
515 SkScalar text_size,
516 tolerance;
517 } kTestSizes[] = {
518 // These gross tolerances are required for the test to pass on NativeFonts bots.
519 // Might be worth investigating why we need so much slack.
520 { 5, 2.0f },
521 { 10, 4.0f },
522 { 15, 5.5f },
523 { 25, 8.0f },
524 };
525
526 struct {
527 skottie::Shaper::VAlign align;
528 SkScalar topFactor;
529 } kTestAligns[] = {
530 { skottie::Shaper::VAlign::kVisualTop , 0.0f },
531 { skottie::Shaper::VAlign::kVisualCenter, 0.5f },
532 // TODO: any way to test kTopBaseline?
533 };
534
535 const SkString text("Foo, bar.\rBaz.");
536 const auto text_box = SkRect::MakeXYWH(100, 100, 1000, 1000); // large-enough to avoid breaks.
537
538
539 for (const auto& tsize : kTestSizes) {
540 for (const auto& talign : kTestAligns) {
541 const skottie::Shaper::TextDesc desc = {
542 typeface,
543 tsize.text_size,
544 0, tsize.text_size,
545 tsize.text_size,
546 0,
547 0,
548 SkTextUtils::Align::kCenter_Align,
549 talign.align,
550 Shaper::ResizePolicy::kNone,
551 Shaper::LinebreakPolicy::kParagraph,
552 Shaper::Direction::kLTR,
553 Shaper::Capitalization::kNone,
554 Shaper::Flags::kNone
555 };
556
557 const auto shape_result = Shaper::Shape(text, desc, text_box, SkFontMgr::RefDefault());
558 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
559 REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
560
561 const auto shape_bounds = ComputeShapeResultBounds(shape_result);
562 REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
563
564 const auto v_diff = text_box.height() - shape_bounds.height();
565
566 const auto expected_t = text_box.top() + v_diff * talign.topFactor;
567 REPORTER_ASSERT(reporter,
568 std::fabs(shape_bounds.top() - expected_t) < tsize.tolerance,
569 "%f %f %f %f %d", shape_bounds.top(), expected_t, tsize.tolerance,
570 tsize.text_size, SkToU32(talign.align));
571
572 const auto expected_b = text_box.bottom() - v_diff * (1 - talign.topFactor);
573 REPORTER_ASSERT(reporter,
574 std::fabs(shape_bounds.bottom() - expected_b) < tsize.tolerance,
575 "%f %f %f %f %d", shape_bounds.bottom(), expected_b, tsize.tolerance,
576 tsize.text_size, SkToU32(talign.align));
577 }
578 }
579 }
580
DEF_TEST(Skottie_Shaper_FragmentGlyphs,reporter)581 DEF_TEST(Skottie_Shaper_FragmentGlyphs, reporter) {
582 skottie::Shaper::TextDesc desc = {
583 SkTypeface::MakeDefault(),
584 18,
585 0, 18,
586 18,
587 0,
588 0,
589 SkTextUtils::Align::kCenter_Align,
590 Shaper::VAlign::kTop,
591 Shaper::ResizePolicy::kNone,
592 Shaper::LinebreakPolicy::kParagraph,
593 Shaper::Direction::kLTR,
594 Shaper::Capitalization::kNone,
595 Shaper::Flags::kNone
596 };
597
598 const SkString text("Foo bar baz");
599 const auto text_box = SkRect::MakeWH(100, 100);
600
601 {
602 const auto shape_result = Shaper::Shape(text, desc, text_box, SkFontMgr::RefDefault());
603 // Default/consolidated mode => single blob result.
604 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
605 REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
606 }
607
608 {
609 desc.fFlags = Shaper::Flags::kFragmentGlyphs;
610 const auto shape_result = skottie::Shaper::Shape(text, desc, text_box,
611 SkFontMgr::RefDefault());
612 // Fragmented mode => one blob per glyph.
613 const size_t expectedSize = text.size();
614 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == expectedSize);
615 for (size_t i = 0; i < expectedSize; ++i) {
616 REPORTER_ASSERT(reporter, shape_result.fFragments[i].fBlob);
617 }
618 }
619 }
620
621 #if defined(SK_SHAPER_HARFBUZZ_AVAILABLE) && !defined(SK_BUILD_FOR_WIN)
622
DEF_TEST(Skottie_Shaper_ExplicitFontMgr,reporter)623 DEF_TEST(Skottie_Shaper_ExplicitFontMgr, reporter) {
624 class CountingFontMgr : public SkFontMgr {
625 public:
626 size_t fallbackCount() const { return fFallbackCount; }
627
628 protected:
629 int onCountFamilies() const override { return 0; }
630 void onGetFamilyName(int index, SkString* familyName) const override {
631 SkDEBUGFAIL("onGetFamilyName called with bad index");
632 }
633 SkFontStyleSet* onCreateStyleSet(int index) const override {
634 SkDEBUGFAIL("onCreateStyleSet called with bad index");
635 return nullptr;
636 }
637 SkFontStyleSet* onMatchFamily(const char[]) const override {
638 return SkFontStyleSet::CreateEmpty();
639 }
640
641 SkTypeface* onMatchFamilyStyle(const char[], const SkFontStyle&) const override {
642 return nullptr;
643 }
644 SkTypeface* onMatchFamilyStyleCharacter(const char familyName[],
645 const SkFontStyle& style,
646 const char* bcp47[],
647 int bcp47Count,
648 SkUnichar character) const override {
649 fFallbackCount++;
650 return nullptr;
651 }
652
653 sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int) const override {
654 return nullptr;
655 }
656 sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>, int) const override {
657 return nullptr;
658 }
659 sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
660 const SkFontArguments&) const override {
661 return nullptr;
662 }
663 sk_sp<SkTypeface> onMakeFromFile(const char[], int) const override {
664 return nullptr;
665 }
666 sk_sp<SkTypeface> onLegacyMakeTypeface(const char [], SkFontStyle) const override {
667 return nullptr;
668 }
669 private:
670 mutable size_t fFallbackCount = 0;
671 };
672
673 auto fontmgr = sk_make_sp<CountingFontMgr>();
674
675 skottie::Shaper::TextDesc desc = {
676 ToolUtils::create_portable_typeface(),
677 18,
678 0, 18,
679 18,
680 0,
681 0,
682 SkTextUtils::Align::kCenter_Align,
683 Shaper::VAlign::kTop,
684 Shaper::ResizePolicy::kNone,
685 Shaper::LinebreakPolicy::kParagraph,
686 Shaper::Direction::kLTR,
687 Shaper::Capitalization::kNone,
688 Shaper::Flags::kNone
689 };
690
691 const auto text_box = SkRect::MakeWH(100, 100);
692
693 {
694 const auto shape_result = Shaper::Shape(SkString("foo bar"), desc, text_box, fontmgr);
695
696 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
697 REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
698 REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 0ul);
699 REPORTER_ASSERT(reporter, shape_result.fMissingGlyphCount == 0);
700 }
701
702 {
703 // An unassigned codepoint should trigger fallback.
704 const auto shape_result = skottie::Shaper::Shape(SkString("foo\U000DFFFFbar"),
705 desc, text_box, fontmgr);
706
707 REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
708 REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
709 REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 1ul);
710 REPORTER_ASSERT(reporter, shape_result.fMissingGlyphCount == 1ul);
711 }
712 }
713
714 #endif
715
DEF_TEST(Skottie_Image_Loading,reporter)716 DEF_TEST(Skottie_Image_Loading, reporter) {
717 class TestResourceProvider final : public skresources::ResourceProvider {
718 public:
719 TestResourceProvider(sk_sp<skresources::ImageAsset> single_asset,
720 sk_sp<skresources::ImageAsset> multi_asset)
721 : fSingleFrameAsset(std::move(single_asset))
722 , fMultiFrameAsset (std::move( multi_asset)) {}
723
724 private:
725 sk_sp<ImageAsset> loadImageAsset(const char path[],
726 const char name[],
727 const char id[]) const override {
728 return strcmp(id, "single_frame")
729 ? fMultiFrameAsset
730 : fSingleFrameAsset;
731 }
732
733 const sk_sp<skresources::ImageAsset> fSingleFrameAsset,
734 fMultiFrameAsset;
735 };
736
737 auto make_animation = [&reporter] (sk_sp<skresources::ImageAsset> single_asset,
738 sk_sp<skresources::ImageAsset> multi_asset,
739 bool deferred_image_loading) {
740 static constexpr char json[] = R"({
741 "v": "5.2.1",
742 "w": 100,
743 "h": 100,
744 "fr": 10,
745 "ip": 0,
746 "op": 100,
747 "assets": [
748 {
749 "id": "single_frame",
750 "p" : "single_frame.png",
751 "u" : "images/",
752 "w" : 500,
753 "h" : 500
754 },
755 {
756 "id": "multi_frame",
757 "p" : "multi_frame.png",
758 "u" : "images/",
759 "w" : 500,
760 "h" : 500
761 }
762 ],
763 "layers": [
764 {
765 "ty": 2,
766 "refId": "single_frame",
767 "ind": 0,
768 "ip": 0,
769 "op": 100,
770 "ks": {}
771 },
772 {
773 "ty": 2,
774 "refId": "multi_frame",
775 "ind": 1,
776 "ip": 0,
777 "op": 100,
778 "ks": {}
779 }
780 ]
781 })";
782
783 SkMemoryStream stream(json, strlen(json));
784
785 const auto flags = deferred_image_loading
786 ? static_cast<uint32_t>(skottie::Animation::Builder::kDeferImageLoading)
787 : 0;
788 auto animation =
789 skottie::Animation::Builder(flags)
790 .setResourceProvider(sk_make_sp<TestResourceProvider>(std::move(single_asset),
791 std::move( multi_asset)))
792 .make(&stream);
793
794 REPORTER_ASSERT(reporter, animation);
795
796 return animation;
797 };
798
799 class TestAsset final : public skresources::ImageAsset {
800 public:
801 explicit TestAsset(bool multi_frame) : fMultiFrame(multi_frame) {}
802
803 const std::vector<float>& requestedFrames() const { return fRequestedFrames; }
804
805 private:
806 bool isMultiFrame() override { return fMultiFrame; }
807
808 sk_sp<SkImage> getFrame(float t) override {
809 fRequestedFrames.push_back(t);
810
811 return SkSurface::MakeRasterN32Premul(10, 10)->makeImageSnapshot();
812 }
813
814 const bool fMultiFrame;
815
816 std::vector<float> fRequestedFrames;
817 };
818
819 {
820 auto single_asset = sk_make_sp<TestAsset>(false),
821 multi_asset = sk_make_sp<TestAsset>(true);
822
823 // Default image loading: single-frame images are loaded upfront, multi-frame images are
824 // loaded on-demand.
825 auto animation = make_animation(single_asset, multi_asset, false);
826
827 REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 1);
828 REPORTER_ASSERT(reporter, multi_asset->requestedFrames().size() == 0);
829 REPORTER_ASSERT(reporter, SkScalarNearlyZero(single_asset->requestedFrames()[0]));
830
831 animation->seekFrameTime(1);
832 REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 1);
833 REPORTER_ASSERT(reporter, multi_asset->requestedFrames().size() == 1);
834 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(multi_asset->requestedFrames()[0], 1));
835
836 animation->seekFrameTime(2);
837 REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 1);
838 REPORTER_ASSERT(reporter, multi_asset->requestedFrames().size() == 2);
839 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(multi_asset->requestedFrames()[1], 2));
840 }
841
842 {
843 auto single_asset = sk_make_sp<TestAsset>(false),
844 multi_asset = sk_make_sp<TestAsset>(true);
845
846 // Deferred image loading: both single-frame and multi-frame images are loaded on-demand.
847 auto animation = make_animation(single_asset, multi_asset, true);
848
849 REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 0);
850 REPORTER_ASSERT(reporter, multi_asset->requestedFrames().size() == 0);
851
852 animation->seekFrameTime(1);
853 REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 1);
854 REPORTER_ASSERT(reporter, multi_asset->requestedFrames().size() == 1);
855 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(single_asset->requestedFrames()[0], 1));
856 REPORTER_ASSERT(reporter, SkScalarNearlyEqual (multi_asset->requestedFrames()[0], 1));
857
858 animation->seekFrameTime(2);
859 REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 1);
860 REPORTER_ASSERT(reporter, multi_asset->requestedFrames().size() == 2);
861 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(multi_asset->requestedFrames()[1], 2));
862 }
863 }
864