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