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