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