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
26using namespace skottie;
27
28DEF_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
40DEF_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
339DEF_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
405static 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
428static 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
439DEF_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
510DEF_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
581DEF_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
623DEF_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
716DEF_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