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