• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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