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[]) override {
177             fCurrentNode = fCurrentNode.empty() ? node_name : fCurrentNode + "." + node_name;
178         }
179 
180         void onLeavingNode(const char node_name[]) override {
181             auto length = strlen(node_name);
182             fCurrentNode =
183                     fCurrentNode.length() > length
184                             ? fCurrentNode.substr(0, fCurrentNode.length() - strlen(node_name) - 1)
185                             : "";
186         }
187 
188         const std::vector<ColorInfo>& colors() const { return fColors; }
189         const std::vector<OpacityInfo>& opacities() const { return fOpacities; }
190         const std::vector<TextInfo>& texts() const { return fTexts; }
191         const std::vector<TransformInfo>& transforms() const { return fTransforms; }
192         const std::vector<ColorInfo>& colorsWithFullKeypath() const {
193             return fColorsWithFullKeypath;
194         }
195 
196     private:
197         std::vector<ColorInfo>     fColors;
198         std::vector<OpacityInfo>   fOpacities;
199         std::vector<TextInfo>      fTexts;
200         std::vector<TransformInfo> fTransforms;
201         std::string                fCurrentNode;
202         std::vector<ColorInfo>     fColorsWithFullKeypath;
203     };
204 
205     // Returns a single specified typeface for all requests.
206     class DummyFontMgr : public SkFontMgr {
207      public:
208         DummyFontMgr(sk_sp<SkTypeface> test_font) : fTestFont(test_font) {}
209 
210         int onCountFamilies() const override { return 1; }
211         void onGetFamilyName(int index, SkString* familyName) const override {}
212         SkFontStyleSet* onCreateStyleSet(int index) const override { return nullptr; }
213         SkFontStyleSet* onMatchFamily(const char familyName[]) const override { return nullptr; }
214         SkTypeface* onMatchFamilyStyle(const char familyName[],
215                                       const SkFontStyle& fontStyle) const override {
216             return nullptr;
217         }
218         SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&,
219                                                 const char* bcp47[], int bcp47Count,
220                                                 SkUnichar character) const override {
221             return nullptr;
222         }
223         SkTypeface* onMatchFaceStyle(const SkTypeface*, const SkFontStyle&) const override {
224             return nullptr;
225         }
226         sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override {
227             return fTestFont;
228         }
229         sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
230                                                     int ttcIndex) const override {
231             return fTestFont;
232         }
233         sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
234                                                    const SkFontArguments&) const override {
235             return fTestFont;
236         }
237         sk_sp<SkTypeface> onMakeFromFontData(std::unique_ptr<SkFontData>) 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<DummyFontMgr> test_font_manager = sk_make_sp<DummyFontMgr>(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,
320       120,
321       12,
322       0,
323       SkTextUtils::kLeft_Align,
324       Shaper::VAlign::kTopBaseline,
325       Shaper::ResizePolicy::kNone,
326       Shaper::LinebreakPolicy::kExplicit,
327       SkRect::MakeEmpty(),
328       SK_ColorTRANSPARENT,
329       SK_ColorTRANSPARENT,
330       TextPaintOrder::kFillStroke,
331       false,
332       false
333     }));
334 }
335 
DEF_TEST(Skottie_Annotations,reporter)336 DEF_TEST(Skottie_Annotations, reporter) {
337     static constexpr char json[] = R"({
338                                      "v": "5.2.1",
339                                      "w": 100,
340                                      "h": 100,
341                                      "fr": 10,
342                                      "ip": 0,
343                                      "op": 100,
344                                      "layers": [
345                                        {
346                                          "ty": 1,
347                                          "ind": 0,
348                                          "ip": 0,
349                                          "op": 1,
350                                          "ks": {
351                                            "o": { "a": 0, "k": 50 }
352                                          },
353                                          "sw": 100,
354                                          "sh": 100,
355                                          "sc": "#ffffff"
356                                        }
357                                      ],
358                                      "markers": [
359                                        {
360                                            "cm": "marker_1",
361                                            "dr": 25,
362                                            "tm": 25
363                                        },
364                                        {
365                                            "cm": "marker_2",
366                                            "dr": 0,
367                                            "tm": 75
368                                        }
369                                      ]
370                                    })";
371 
372     class TestMarkerObserver final : public MarkerObserver {
373     public:
374         void onMarker(const char name[], float t0, float t1) override {
375             fMarkers.push_back(std::make_tuple(name, t0, t1));
376         }
377 
378         std::vector<std::tuple<std::string, float, float>> fMarkers;
379     };
380 
381     SkMemoryStream stream(json, strlen(json));
382     auto observer = sk_make_sp<TestMarkerObserver>();
383 
384     auto animation = skottie::Animation::Builder()
385             .setMarkerObserver(observer)
386             .make(&stream);
387 
388     REPORTER_ASSERT(reporter, animation);
389     REPORTER_ASSERT(reporter, animation->duration() == 10);
390     REPORTER_ASSERT(reporter, animation->inPoint()  == 0.0);
391     REPORTER_ASSERT(reporter, animation->outPoint() == 100.0);
392 
393     REPORTER_ASSERT(reporter, observer->fMarkers.size() == 2ul);
394     REPORTER_ASSERT(reporter, std::get<0>(observer->fMarkers[0]) == "marker_1");
395     REPORTER_ASSERT(reporter, std::get<1>(observer->fMarkers[0]) == 0.25f);
396     REPORTER_ASSERT(reporter, std::get<2>(observer->fMarkers[0]) == 0.50f);
397     REPORTER_ASSERT(reporter, std::get<0>(observer->fMarkers[1]) == "marker_2");
398     REPORTER_ASSERT(reporter, std::get<1>(observer->fMarkers[1]) == 0.75f);
399     REPORTER_ASSERT(reporter, std::get<2>(observer->fMarkers[1]) == 0.75f);
400 }
401 
ComputeBlobBounds(const sk_sp<SkTextBlob> & blob)402 static SkRect ComputeBlobBounds(const sk_sp<SkTextBlob>& blob) {
403     auto bounds = SkRect::MakeEmpty();
404 
405     if (!blob) {
406         return bounds;
407     }
408 
409     SkAutoSTArray<16, SkRect> glyphBounds;
410 
411     SkTextBlobRunIterator it(blob.get());
412 
413     for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
414         glyphBounds.reset(SkToInt(it.glyphCount()));
415         it.font().getBounds(it.glyphs(), it.glyphCount(), glyphBounds.get(), nullptr);
416 
417         SkASSERT(it.positioning() == SkTextBlobRunIterator::kFull_Positioning);
418         for (uint32_t i = 0; i < it.glyphCount(); ++i) {
419             bounds.join(glyphBounds[i].makeOffset(it.pos()[i * 2    ],
420                                                   it.pos()[i * 2 + 1]));
421         }
422     }
423 
424     return bounds;
425 }
426 
ComputeShapeResultBounds(const skottie::Shaper::Result & res)427 static SkRect ComputeShapeResultBounds(const skottie::Shaper::Result& res) {
428     auto bounds = SkRect::MakeEmpty();
429 
430     for (const auto& fragment : res.fFragments) {
431         bounds.join(ComputeBlobBounds(fragment.fBlob).makeOffset(fragment.fPos.x(),
432                                                                  fragment.fPos.y()));
433     }
434 
435     return bounds;
436 }
437 
DEF_TEST(Skottie_Shaper_HAlign,reporter)438 DEF_TEST(Skottie_Shaper_HAlign, reporter) {
439     auto typeface = SkTypeface::MakeDefault();
440     REPORTER_ASSERT(reporter, typeface);
441 
442     static constexpr struct {
443         SkScalar text_size,
444                  tolerance;
445     } kTestSizes[] = {
446         // These gross tolerances are required for the test to pass on NativeFonts bots.
447         // Might be worth investigating why we need so much slack.
448         {  5, 2.0f },
449         { 10, 2.0f },
450         { 15, 2.4f },
451         { 25, 4.4f },
452     };
453 
454     static constexpr struct {
455         SkTextUtils::Align align;
456         SkScalar           l_selector,
457                            r_selector;
458     } kTestAligns[] = {
459         { SkTextUtils::  kLeft_Align, 0.0f, 1.0f },
460         { SkTextUtils::kCenter_Align, 0.5f, 0.5f },
461         { SkTextUtils:: kRight_Align, 1.0f, 0.0f },
462     };
463 
464     const SkString text("Foo, bar.\rBaz.");
465     const SkPoint  text_point = SkPoint::Make(100, 100);
466 
467     for (const auto& tsize : kTestSizes) {
468         for (const auto& talign : kTestAligns) {
469             const skottie::Shaper::TextDesc desc = {
470                 typeface,
471                 tsize.text_size,
472                 tsize.text_size,
473                 0,
474                 0,
475                 talign.align,
476                 Shaper::VAlign::kTopBaseline,
477                 Shaper::ResizePolicy::kNone,
478                 Shaper::LinebreakPolicy::kExplicit,
479                 Shaper::Flags::kNone
480             };
481 
482             const auto shape_result = Shaper::Shape(text, desc, text_point,
483                                                     SkFontMgr::RefDefault());
484             REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
485             REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
486 
487             const auto shape_bounds = ComputeShapeResultBounds(shape_result);
488             REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
489 
490             const auto expected_l = text_point.x() - shape_bounds.width() * talign.l_selector;
491             REPORTER_ASSERT(reporter,
492                             std::fabs(shape_bounds.left() - expected_l) < tsize.tolerance,
493                             "%f %f %f %f %d", shape_bounds.left(), expected_l, tsize.tolerance,
494                                               tsize.text_size, talign.align);
495 
496             const auto expected_r = text_point.x() + shape_bounds.width() * talign.r_selector;
497             REPORTER_ASSERT(reporter,
498                             std::fabs(shape_bounds.right() - expected_r) < tsize.tolerance,
499                             "%f %f %f %f %d", shape_bounds.right(), expected_r, tsize.tolerance,
500                                               tsize.text_size, talign.align);
501 
502         }
503     }
504 }
505 
DEF_TEST(Skottie_Shaper_VAlign,reporter)506 DEF_TEST(Skottie_Shaper_VAlign, reporter) {
507     auto typeface = SkTypeface::MakeDefault();
508     REPORTER_ASSERT(reporter, typeface);
509 
510     static constexpr struct {
511         SkScalar text_size,
512                  tolerance;
513     } kTestSizes[] = {
514         // These gross tolerances are required for the test to pass on NativeFonts bots.
515         // Might be worth investigating why we need so much slack.
516         {  5, 2.0f },
517         { 10, 4.0f },
518         { 15, 5.5f },
519         { 25, 8.0f },
520     };
521 
522     struct {
523         skottie::Shaper::VAlign align;
524         SkScalar                topFactor;
525     } kTestAligns[] = {
526         { skottie::Shaper::VAlign::kVisualTop   , 0.0f },
527         { skottie::Shaper::VAlign::kVisualCenter, 0.5f },
528         // TODO: any way to test kTopBaseline?
529     };
530 
531     const SkString text("Foo, bar.\rBaz.");
532     const auto text_box = SkRect::MakeXYWH(100, 100, 1000, 1000); // large-enough to avoid breaks.
533 
534 
535     for (const auto& tsize : kTestSizes) {
536         for (const auto& talign : kTestAligns) {
537             const skottie::Shaper::TextDesc desc = {
538                 typeface,
539                 tsize.text_size,
540                 tsize.text_size,
541                 0,
542                 0,
543                 SkTextUtils::Align::kCenter_Align,
544                 talign.align,
545                 Shaper::ResizePolicy::kNone,
546                 Shaper::LinebreakPolicy::kParagraph,
547                 Shaper::Flags::kNone
548             };
549 
550             const auto shape_result = Shaper::Shape(text, desc, text_box, SkFontMgr::RefDefault());
551             REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
552             REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
553 
554             const auto shape_bounds = ComputeShapeResultBounds(shape_result);
555             REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
556 
557             const auto v_diff = text_box.height() - shape_bounds.height();
558 
559             const auto expected_t = text_box.top() + v_diff * talign.topFactor;
560             REPORTER_ASSERT(reporter,
561                             std::fabs(shape_bounds.top() - expected_t) < tsize.tolerance,
562                             "%f %f %f %f %d", shape_bounds.top(), expected_t, tsize.tolerance,
563                                               tsize.text_size, SkToU32(talign.align));
564 
565             const auto expected_b = text_box.bottom() - v_diff * (1 - talign.topFactor);
566             REPORTER_ASSERT(reporter,
567                             std::fabs(shape_bounds.bottom() - expected_b) < tsize.tolerance,
568                             "%f %f %f %f %d", shape_bounds.bottom(), expected_b, tsize.tolerance,
569                                               tsize.text_size, SkToU32(talign.align));
570         }
571     }
572 }
573 
DEF_TEST(Skottie_Shaper_FragmentGlyphs,reporter)574 DEF_TEST(Skottie_Shaper_FragmentGlyphs, reporter) {
575     skottie::Shaper::TextDesc desc = {
576         SkTypeface::MakeDefault(),
577         18,
578         18,
579          0,
580          0,
581         SkTextUtils::Align::kCenter_Align,
582         Shaper::VAlign::kTop,
583         Shaper::ResizePolicy::kNone,
584         Shaper::LinebreakPolicy::kParagraph,
585         Shaper::Flags::kNone
586     };
587 
588     const SkString text("Foo bar baz");
589     const auto text_box = SkRect::MakeWH(100, 100);
590 
591     {
592         const auto shape_result = Shaper::Shape(text, desc, text_box, SkFontMgr::RefDefault());
593         // Default/consolidated mode => single blob result.
594         REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
595         REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
596     }
597 
598     {
599         desc.fFlags = Shaper::Flags::kFragmentGlyphs;
600         const auto shape_result = skottie::Shaper::Shape(text, desc, text_box,
601                                                          SkFontMgr::RefDefault());
602         // Fragmented mode => one blob per glyph.
603         const size_t expectedSize = text.size();
604         REPORTER_ASSERT(reporter, shape_result.fFragments.size() == expectedSize);
605         for (size_t i = 0; i < expectedSize; ++i) {
606             REPORTER_ASSERT(reporter, shape_result.fFragments[i].fBlob);
607         }
608     }
609 }
610 
611 #if defined(SK_SHAPER_HARFBUZZ_AVAILABLE) && !defined(SK_BUILD_FOR_WIN)
612 
DEF_TEST(Skottie_Shaper_ExplicitFontMgr,reporter)613 DEF_TEST(Skottie_Shaper_ExplicitFontMgr, reporter) {
614     class CountingFontMgr : public SkFontMgr {
615     public:
616         size_t fallbackCount() const { return fFallbackCount; }
617 
618     protected:
619         int onCountFamilies() const override { return 0; }
620         void onGetFamilyName(int index, SkString* familyName) const override {
621             SkDEBUGFAIL("onGetFamilyName called with bad index");
622         }
623         SkFontStyleSet* onCreateStyleSet(int index) const override {
624             SkDEBUGFAIL("onCreateStyleSet called with bad index");
625             return nullptr;
626         }
627         SkFontStyleSet* onMatchFamily(const char[]) const override {
628             return SkFontStyleSet::CreateEmpty();
629         }
630 
631         SkTypeface* onMatchFamilyStyle(const char[], const SkFontStyle&) const override {
632             return nullptr;
633         }
634         SkTypeface* onMatchFamilyStyleCharacter(const char familyName[],
635                                                 const SkFontStyle& style,
636                                                 const char* bcp47[],
637                                                 int bcp47Count,
638                                                 SkUnichar character) const override {
639             fFallbackCount++;
640             return nullptr;
641         }
642         SkTypeface* onMatchFaceStyle(const SkTypeface*, const SkFontStyle&) const override {
643             return nullptr;
644         }
645 
646         sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int) const override {
647             return nullptr;
648         }
649         sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>, int) const override {
650             return nullptr;
651         }
652         sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
653                                                const SkFontArguments&) const override {
654             return nullptr;
655         }
656         sk_sp<SkTypeface> onMakeFromFontData(std::unique_ptr<SkFontData>) const override {
657             return nullptr;
658         }
659         sk_sp<SkTypeface> onMakeFromFile(const char[], int) const override {
660             return nullptr;
661         }
662         sk_sp<SkTypeface> onLegacyMakeTypeface(const char [], SkFontStyle) const override {
663             return nullptr;
664         }
665     private:
666         mutable size_t fFallbackCount = 0;
667     };
668 
669     auto fontmgr = sk_make_sp<CountingFontMgr>();
670 
671     skottie::Shaper::TextDesc desc = {
672         ToolUtils::create_portable_typeface(),
673         18,
674         18,
675          0,
676          0,
677         SkTextUtils::Align::kCenter_Align,
678         Shaper::VAlign::kTop,
679         Shaper::ResizePolicy::kNone,
680         Shaper::LinebreakPolicy::kParagraph,
681         Shaper::Flags::kNone
682     };
683 
684     const auto text_box = SkRect::MakeWH(100, 100);
685 
686     {
687         const auto shape_result = Shaper::Shape(SkString("foo bar"), desc, text_box, fontmgr);
688 
689         REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
690         REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
691         REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 0ul);
692         REPORTER_ASSERT(reporter, shape_result.fMissingGlyphCount == 0);
693     }
694 
695     {
696         // An unassigned codepoint should trigger fallback.
697         const auto shape_result = skottie::Shaper::Shape(SkString("foo\U000DFFFFbar"),
698                                                          desc, text_box, fontmgr);
699 
700         REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
701         REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
702         REPORTER_ASSERT(reporter, fontmgr->fallbackCount() == 1ul);
703         REPORTER_ASSERT(reporter, shape_result.fMissingGlyphCount == 1ul);
704     }
705 }
706 
707 #endif
708 
DEF_TEST(Skottie_Image_Loading,reporter)709 DEF_TEST(Skottie_Image_Loading, reporter) {
710     class TestResourceProvider final : public skresources::ResourceProvider {
711     public:
712         TestResourceProvider(sk_sp<skresources::ImageAsset> single_asset,
713                              sk_sp<skresources::ImageAsset>  multi_asset)
714             : fSingleFrameAsset(std::move(single_asset))
715             , fMultiFrameAsset (std::move( multi_asset)) {}
716 
717     private:
718         sk_sp<ImageAsset> loadImageAsset(const char path[],
719                                          const char name[],
720                                          const char id[]) const override {
721             return strcmp(id, "single_frame")
722                     ? fMultiFrameAsset
723                     : fSingleFrameAsset;
724         }
725 
726         const sk_sp<skresources::ImageAsset> fSingleFrameAsset,
727                                              fMultiFrameAsset;
728     };
729 
730     auto make_animation = [&reporter] (sk_sp<skresources::ImageAsset> single_asset,
731                                        sk_sp<skresources::ImageAsset>  multi_asset,
732                                        bool deferred_image_loading) {
733         static constexpr char json[] = R"({
734                                          "v": "5.2.1",
735                                          "w": 100,
736                                          "h": 100,
737                                          "fr": 10,
738                                          "ip": 0,
739                                          "op": 100,
740                                          "assets": [
741                                            {
742                                              "id": "single_frame",
743                                              "p" : "single_frame.png",
744                                              "u" : "images/",
745                                              "w" : 500,
746                                              "h" : 500
747                                            },
748                                            {
749                                              "id": "multi_frame",
750                                              "p" : "multi_frame.png",
751                                              "u" : "images/",
752                                              "w" : 500,
753                                              "h" : 500
754                                            }
755                                          ],
756                                          "layers": [
757                                            {
758                                              "ty": 2,
759                                              "refId": "single_frame",
760                                              "ind": 0,
761                                              "ip": 0,
762                                              "op": 100,
763                                              "ks": {}
764                                            },
765                                            {
766                                              "ty": 2,
767                                              "refId": "multi_frame",
768                                              "ind": 1,
769                                              "ip": 0,
770                                              "op": 100,
771                                              "ks": {}
772                                            }
773                                          ]
774                                        })";
775 
776         SkMemoryStream stream(json, strlen(json));
777 
778         const auto flags = deferred_image_loading
779             ? static_cast<uint32_t>(skottie::Animation::Builder::kDeferImageLoading)
780             : 0;
781         auto animation =
782             skottie::Animation::Builder(flags)
783                 .setResourceProvider(sk_make_sp<TestResourceProvider>(std::move(single_asset),
784                                                                       std::move( multi_asset)))
785                 .make(&stream);
786 
787         REPORTER_ASSERT(reporter, animation);
788 
789         return  animation;
790     };
791 
792     class TestAsset final : public skresources::ImageAsset {
793     public:
794         explicit TestAsset(bool multi_frame) : fMultiFrame(multi_frame) {}
795 
796         const std::vector<float>& requestedFrames() const { return fRequestedFrames; }
797 
798     private:
799         bool isMultiFrame() override { return fMultiFrame; }
800 
801         sk_sp<SkImage> getFrame(float t) override {
802             fRequestedFrames.push_back(t);
803 
804             return SkSurface::MakeRasterN32Premul(10, 10)->makeImageSnapshot();
805         }
806 
807         const bool fMultiFrame;
808 
809         std::vector<float> fRequestedFrames;
810     };
811 
812     {
813         auto single_asset = sk_make_sp<TestAsset>(false),
814               multi_asset = sk_make_sp<TestAsset>(true);
815 
816         // Default image loading: single-frame images are loaded upfront, multi-frame images are
817         // loaded on-demand.
818         auto animation = make_animation(single_asset, multi_asset, false);
819 
820         REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 1);
821         REPORTER_ASSERT(reporter,  multi_asset->requestedFrames().size() == 0);
822         REPORTER_ASSERT(reporter, SkScalarNearlyZero(single_asset->requestedFrames()[0]));
823 
824         animation->seekFrameTime(1);
825         REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 1);
826         REPORTER_ASSERT(reporter,  multi_asset->requestedFrames().size() == 1);
827         REPORTER_ASSERT(reporter, SkScalarNearlyEqual(multi_asset->requestedFrames()[0], 1));
828 
829         animation->seekFrameTime(2);
830         REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 1);
831         REPORTER_ASSERT(reporter,  multi_asset->requestedFrames().size() == 2);
832         REPORTER_ASSERT(reporter, SkScalarNearlyEqual(multi_asset->requestedFrames()[1], 2));
833     }
834 
835     {
836         auto single_asset = sk_make_sp<TestAsset>(false),
837               multi_asset = sk_make_sp<TestAsset>(true);
838 
839         // Deferred image loading: both single-frame and multi-frame images are loaded on-demand.
840         auto animation = make_animation(single_asset, multi_asset, true);
841 
842         REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 0);
843         REPORTER_ASSERT(reporter,  multi_asset->requestedFrames().size() == 0);
844 
845         animation->seekFrameTime(1);
846         REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 1);
847         REPORTER_ASSERT(reporter,  multi_asset->requestedFrames().size() == 1);
848         REPORTER_ASSERT(reporter, SkScalarNearlyEqual(single_asset->requestedFrames()[0], 1));
849         REPORTER_ASSERT(reporter, SkScalarNearlyEqual (multi_asset->requestedFrames()[0], 1));
850 
851         animation->seekFrameTime(2);
852         REPORTER_ASSERT(reporter, single_asset->requestedFrames().size() == 1);
853         REPORTER_ASSERT(reporter,  multi_asset->requestedFrames().size() == 2);
854         REPORTER_ASSERT(reporter, SkScalarNearlyEqual(multi_asset->requestedFrames()[1], 2));
855     }
856 }
857