Flutter Engine
 
Loading...
Searching...
No Matches
aiks_dl_text_unittests.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
26
28#include "txt/platform.h"
29
30using namespace flutter;
31/////////////////////////////////////////////////////
32
33namespace impeller {
34namespace testing {
35
37 bool stroke = false;
42 std::shared_ptr<DlMaskFilter> filter;
43 bool is_subpixel = false;
44};
45
46bool RenderTextInCanvasSkia(const std::shared_ptr<Context>& context,
47 DisplayListBuilder& canvas,
48 const std::string& text,
49 const std::string_view& font_fixture,
50 const TextRenderOptions& options = {},
51 const std::optional<SkFont>& font = std::nullopt) {
52 // Draw the baseline.
53 DlPaint paint;
54 paint.setColor(DlColor::kAqua().withAlpha(255 * 0.25));
55 canvas.DrawRect(
56 DlRect::MakeXYWH(options.position.x - 50, options.position.y, 900, 10),
57 paint);
58
59 // Mark the point at which the text is drawn.
60 paint.setColor(DlColor::kRed().withAlpha(255 * 0.25));
61 canvas.DrawCircle(options.position, 5.0, paint);
62
63 // Construct the text blob.
64 SkFont selected_font;
65 if (!font.has_value()) {
66 auto c_font_fixture = std::string(font_fixture);
67 auto mapping =
68 flutter::testing::OpenFixtureAsSkData(c_font_fixture.c_str());
69 if (!mapping) {
70 return false;
71 }
72 sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
73 selected_font = SkFont(font_mgr->makeFromData(mapping), options.font_size);
74 if (options.is_subpixel) {
75 selected_font.setSubpixel(true);
76 }
77 } else {
78 selected_font = font.value();
79 }
80 auto blob = SkTextBlob::MakeFromString(text.c_str(), selected_font);
81 if (!blob) {
82 return false;
83 }
84
85 // Create the Impeller text frame and draw it at the designated baseline.
86 auto frame = MakeTextFrameFromTextBlobSkia(blob);
87
88 DlPaint text_paint;
89 text_paint.setColor(options.color);
90 text_paint.setMaskFilter(options.filter);
91 text_paint.setStrokeWidth(options.stroke_width);
92 text_paint.setDrawStyle(options.stroke ? DlDrawStyle::kStroke
94 canvas.DrawText(DlTextImpeller::Make(frame), options.position.x,
95 options.position.y, text_paint);
96 return true;
97}
98
99TEST_P(AiksTest, CanRenderTextFrame) {
100 DisplayListBuilder builder;
101
102 DlPaint paint;
103 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
104 builder.DrawPaint(paint);
105 ASSERT_TRUE(RenderTextInCanvasSkia(
106 GetContext(), builder, "the quick brown fox jumped over the lazy dog!.?",
107 "Roboto-Regular.ttf"));
108
109 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
110}
111
112TEST_P(AiksTest, CanRenderTextFrameWithInvertedTransform) {
113 DisplayListBuilder builder;
114
115 DlPaint paint;
116 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
117 builder.DrawPaint(paint);
118 builder.Translate(1000, 0);
119 builder.Scale(-1, 1);
120
121 ASSERT_TRUE(RenderTextInCanvasSkia(
122 GetContext(), builder, "the quick brown fox jumped over the lazy dog!.?",
123 "Roboto-Regular.ttf"));
124
125 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
126}
127
128TEST_P(AiksTest, CanRenderStrokedTextFrame) {
129 DisplayListBuilder builder;
130
131 DlPaint paint;
132 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
133 builder.DrawPaint(paint);
134
135 ASSERT_TRUE(RenderTextInCanvasSkia(
136 GetContext(), builder, "the quick brown fox jumped over the lazy dog!.?",
137 "Roboto-Regular.ttf",
138 {
139 .stroke = true,
140 }));
141 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
142}
143
144TEST_P(AiksTest, CanRenderTextStrokeWidth) {
145 DisplayListBuilder builder;
146
147 DlPaint paint;
148 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
149 builder.DrawPaint(paint);
150
151 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), builder, "LMNOP VWXYZ",
152 "Roboto-Medium.ttf",
153 {
154 .stroke = true,
155 .stroke_width = 4,
156 }));
157 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
158}
159
160TEST_P(AiksTest, CanRenderTextFrameWithHalfScaling) {
161 DisplayListBuilder builder;
162
163 DlPaint paint;
164 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
165 builder.DrawPaint(paint);
166 builder.Scale(0.5, 0.5);
167
168 ASSERT_TRUE(RenderTextInCanvasSkia(
169 GetContext(), builder, "the quick brown fox jumped over the lazy dog!.?",
170 "Roboto-Regular.ttf"));
171 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
172}
173
174// This is a test that looks for glyph artifacts we've see.
175TEST_P(AiksTest, ScaledK) {
176 DisplayListBuilder builder;
177 DlPaint paint;
178 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
179 builder.DrawPaint(paint);
180 for (int i = 0; i < 6; ++i) {
181 builder.Save();
182 builder.Translate(300 * i, 0);
183 Scalar scale = 0.445 - (i / 1000.f);
184 builder.Scale(scale, scale);
186 GetContext(), builder, "k", "Roboto-Regular.ttf",
187 TextRenderOptions{.font_size = 600, .position = DlPoint(10, 500)});
189 GetContext(), builder, "k", "Roboto-Regular.ttf",
190 TextRenderOptions{.font_size = 300, .position = DlPoint(10, 800)});
191 builder.Restore();
192 }
193 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
194}
195
196// This is a test that looks for glyph artifacts we've see.
197TEST_P(AiksTest, MassiveScaleConvertToPath) {
198 Scalar scale = 16.0;
199 auto callback = [&]() -> sk_sp<DisplayList> {
200 if (AiksTest::ImGuiBegin("Controls", nullptr,
201 ImGuiWindowFlags_AlwaysAutoResize)) {
202 ImGui::SliderFloat("Scale", &scale, 4, 20);
203 ImGui::End();
204 }
205
206 DisplayListBuilder builder;
207 DlPaint paint;
208 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
209 builder.DrawPaint(paint);
210
211 builder.Scale(scale, scale);
213 GetContext(), builder, "HELLO", "Roboto-Regular.ttf",
215 .color = (16 * scale >= 250) ? DlColor::kYellow()
217 .position = DlPoint(0, 20)});
218 return builder.Build();
219 };
220
221 ASSERT_TRUE(OpenPlaygroundHere(callback));
222}
223
224TEST_P(AiksTest, CanRenderTextFrameWithScalingOverflow) {
225 Scalar scale = 60.0;
226 Scalar offsetx = -500.0;
227 Scalar offsety = 700.0;
228 auto callback = [&]() -> sk_sp<DisplayList> {
229 if (AiksTest::ImGuiBegin("Controls", nullptr,
230 ImGuiWindowFlags_AlwaysAutoResize)) {
231 ImGui::SliderFloat("scale", &scale, 1.f, 300.f);
232 ImGui::SliderFloat("offsetx", &offsetx, -600.f, 100.f);
233 ImGui::SliderFloat("offsety", &offsety, 600.f, 2048.f);
234 ImGui::End();
235 }
236 DisplayListBuilder builder;
237 builder.Scale(GetContentScale().x, GetContentScale().y);
238 DlPaint paint;
239 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
240 builder.DrawPaint(paint);
241 builder.Scale(scale, scale);
242
244 GetContext(), builder, "test", "Roboto-Regular.ttf",
246 .position = DlPoint(offsetx / scale, offsety / scale),
247 });
248 return builder.Build();
249 };
250 ASSERT_TRUE(OpenPlaygroundHere(callback));
251}
252
253TEST_P(AiksTest, CanRenderTextFrameWithFractionScaling) {
254 Scalar fine_scale = 0.f;
255 bool is_subpixel = false;
256 auto callback = [&]() -> sk_sp<DisplayList> {
257 if (AiksTest::ImGuiBegin("Controls", nullptr,
258 ImGuiWindowFlags_AlwaysAutoResize)) {
259 ImGui::SliderFloat("Fine Scale", &fine_scale, -1, 1);
260 ImGui::Checkbox("subpixel", &is_subpixel);
261 ImGui::End();
262 }
263
264 DisplayListBuilder builder;
265 DlPaint paint;
266 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
267 builder.DrawPaint(paint);
268 Scalar scale = 2.625 + fine_scale;
269 builder.Scale(scale, scale);
270 RenderTextInCanvasSkia(GetContext(), builder,
271 "the quick brown fox jumped over the lazy dog!.?",
272 "Roboto-Regular.ttf",
274 return builder.Build();
275 };
276
277 ASSERT_TRUE(OpenPlaygroundHere(callback));
278}
279
280// https://github.com/flutter/flutter/issues/164958
281TEST_P(AiksTest, TextRotated180Degrees) {
282 float fpivot[2] = {200 + 30, 200 - 20};
283 float rotation = 180;
284 float foffset[2] = {200, 200};
285
286 auto callback = [&]() -> sk_sp<DisplayList> {
287 if (AiksTest::ImGuiBegin("Controls", nullptr,
288 ImGuiWindowFlags_AlwaysAutoResize)) {
289 ImGui::SliderFloat("pivotx", &fpivot[0], 0, 300);
290 ImGui::SliderFloat("pivoty", &fpivot[1], 0, 300);
291 ImGui::SliderFloat("rotation", &rotation, 0, 360);
292 ImGui::SliderFloat("foffsetx", &foffset[0], 0, 300);
293 ImGui::SliderFloat("foffsety", &foffset[1], 0, 300);
294 ImGui::End();
295 }
296 DisplayListBuilder builder;
297 builder.Scale(GetContentScale().x, GetContentScale().y);
298 builder.DrawPaint(DlPaint().setColor(DlColor(0xffffeeff)));
299
300 builder.Save();
301 DlPoint pivot = Point(fpivot[0], fpivot[1]);
302 builder.Translate(pivot.x, pivot.y);
303 builder.Rotate(rotation);
304 builder.Translate(-pivot.x, -pivot.y);
305
306 RenderTextInCanvasSkia(GetContext(), builder, "test", "Roboto-Regular.ttf",
309 .position = DlPoint(foffset[0], foffset[1]),
310 });
311
312 builder.Restore();
313 return builder.Build();
314 };
315 ASSERT_TRUE(OpenPlaygroundHere(callback));
316}
317
318TEST_P(AiksTest, TextFrameSubpixelAlignment) {
319 // "Random" numbers between 0 and 1. Hardcoded to avoid flakiness in goldens.
320 std::array<Scalar, 20> phase_offsets = {
321 7.82637e-06, 0.131538, 0.755605, 0.45865, 0.532767,
322 0.218959, 0.0470446, 0.678865, 0.679296, 0.934693,
323 0.383502, 0.519416, 0.830965, 0.0345721, 0.0534616,
324 0.5297, 0.671149, 0.00769819, 0.383416, 0.0668422};
325 auto callback = [&]() -> sk_sp<DisplayList> {
326 static float font_size = 20;
327 static float phase_variation = 0.2;
328 static float speed = 0.5;
329 static float magnitude = 100;
330 if (AiksTest::ImGuiBegin("Controls", nullptr,
331 ImGuiWindowFlags_AlwaysAutoResize)) {
332 ImGui::SliderFloat("Font size", &font_size, 5, 50);
333 ImGui::SliderFloat("Phase variation", &phase_variation, 0, 1);
334 ImGui::SliderFloat("Oscillation speed", &speed, 0, 2);
335 ImGui::SliderFloat("Oscillation magnitude", &magnitude, 0, 300);
336 ImGui::End();
337 }
338
339 DisplayListBuilder builder;
340 builder.Scale(GetContentScale().x, GetContentScale().y);
341
342 for (size_t i = 0; i < phase_offsets.size(); i++) {
343 DlPoint position = DlPoint(
344 200 +
345 magnitude * std::sin((-phase_offsets[i] * k2Pi * phase_variation +
346 GetSecondsElapsed() * speed)), //
347 200 + i * font_size * 1.1 //
348 );
350 GetContext(), builder,
351 "the quick brown fox jumped over "
352 "the lazy dog!.?",
353 "Roboto-Regular.ttf",
354 {.font_size = font_size, .position = position})) {
355 return nullptr;
356 }
357 }
358 return builder.Build();
359 };
360
361 ASSERT_TRUE(OpenPlaygroundHere(callback));
362}
363
364TEST_P(AiksTest, CanRenderItalicizedText) {
365 DisplayListBuilder builder;
366
367 DlPaint paint;
368 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
369 builder.DrawPaint(paint);
370
371 ASSERT_TRUE(RenderTextInCanvasSkia(
372 GetContext(), builder, "the quick brown fox jumped over the lazy dog!.?",
373 "HomemadeApple.ttf"));
374 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
375}
376
377static constexpr std::string_view kFontFixture =
378#if FML_OS_MACOSX
379 "Apple Color Emoji.ttc";
380#else
381 "NotoColorEmoji.ttf";
382#endif
383
384TEST_P(AiksTest, CanRenderEmojiTextFrame) {
385 DisplayListBuilder builder;
386
387 DlPaint paint;
388 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
389 builder.DrawPaint(paint);
390
391 ASSERT_TRUE(RenderTextInCanvasSkia(
392 GetContext(), builder, "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 😊", kFontFixture));
393 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
394}
395
396TEST_P(AiksTest, CanRenderEmojiTextFrameWithBlur) {
397 DisplayListBuilder builder;
398
399 builder.Scale(GetContentScale().x, GetContentScale().y);
400 DlPaint paint;
401 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
402 builder.DrawPaint(paint);
403
404 ASSERT_TRUE(RenderTextInCanvasSkia(
405 GetContext(), builder, "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 😊", kFontFixture,
408 .filter = DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 4)}));
409 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
410}
411
412TEST_P(AiksTest, CanRenderEmojiTextFrameWithAlpha) {
413 DisplayListBuilder builder;
414
415 DlPaint paint;
416 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
417 builder.DrawPaint(paint);
418
419 ASSERT_TRUE(RenderTextInCanvasSkia(
420 GetContext(), builder, "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 😊", kFontFixture,
421 {.color = DlColor::kBlack().modulateOpacity(0.5)}));
422 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
423}
424
425TEST_P(AiksTest, CanRenderTextInSaveLayer) {
426 DisplayListBuilder builder;
427
428 DlPaint paint;
429 paint.setColor(DlColor::ARGB(0.1, 0.1, 0.1, 0.1));
430 builder.DrawPaint(paint);
431
432 builder.Translate(100, 100);
433 builder.Scale(0.5, 0.5);
434
435 // Blend the layer with the parent pass using kClear to expose the coverage.
436 paint.setBlendMode(DlBlendMode::kClear);
437 builder.SaveLayer(std::nullopt, &paint);
438 ASSERT_TRUE(RenderTextInCanvasSkia(
439 GetContext(), builder, "the quick brown fox jumped over the lazy dog!.?",
440 "Roboto-Regular.ttf"));
441 builder.Restore();
442
443 // Render the text again over the cleared coverage rect.
444 ASSERT_TRUE(RenderTextInCanvasSkia(
445 GetContext(), builder, "the quick brown fox jumped over the lazy dog!.?",
446 "Roboto-Regular.ttf"));
447
448 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
449}
450
451TEST_P(AiksTest, CanRenderTextOutsideBoundaries) {
452 DisplayListBuilder builder;
453 builder.Translate(200, 150);
454
455 // Construct the text blob.
456 auto mapping = flutter::testing::OpenFixtureAsSkData("wtf.otf");
457 ASSERT_NE(mapping, nullptr);
458
459 Scalar font_size = 80;
460 sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
461 SkFont sk_font(font_mgr->makeFromData(mapping), font_size);
462
463 DlPaint text_paint;
464 text_paint.setColor(DlColor::kBlue().withAlpha(255 * 0.8));
465
466 struct {
467 DlPoint position;
468 const char* text;
469 } text[] = {{DlPoint(0, 0), "0F0F0F0"},
470 {DlPoint(1, 2), "789"},
471 {DlPoint(1, 3), "456"},
472 {DlPoint(1, 4), "123"},
473 {DlPoint(0, 6), "0F0F0F0"}};
474 for (auto& t : text) {
475 builder.Save();
476 builder.Translate(t.position.x * font_size * 2,
477 t.position.y * font_size * 1.1);
478 {
479 auto blob = SkTextBlob::MakeFromString(t.text, sk_font);
480 ASSERT_NE(blob, nullptr);
481 auto frame = MakeTextFrameFromTextBlobSkia(blob);
482 builder.DrawText(DlTextImpeller::Make(frame), 0, 0, text_paint);
483 }
484 builder.Restore();
485 }
486
487 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
488}
489
490TEST_P(AiksTest, TextRotated) {
491 DisplayListBuilder builder;
492
493 builder.Scale(GetContentScale().x, GetContentScale().y);
494 DlPaint paint;
495 paint.setColor(DlColor::ARGB(0.1, 0.1, 0.1, 1.0));
496 builder.DrawPaint(paint);
497
498 builder.Transform(Matrix(0.25, -0.3, 0, -0.002, //
499 0, 0.5, 0, 0, //
500 0, 0, 0.3, 0, //
501 100, 100, 0, 1.3));
502 ASSERT_TRUE(RenderTextInCanvasSkia(
503 GetContext(), builder, "the quick brown fox jumped over the lazy dog!.?",
504 "Roboto-Regular.ttf"));
505
506 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
507}
508
509TEST_P(AiksTest, DrawScaledTextWithPerspectiveNoSaveLayer) {
510 DisplayListBuilder builder;
511
512 Matrix matrix = Matrix(1.0, 0.0, 0.0, 0.0, //
513 0.0, 1.0, 0.0, 0.0, //
514 0.0, 0.0, 1.0, 0.01, //
515 0.0, 0.0, 0.0, 1.0) * //
517
518 builder.Transform(matrix);
519
520 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), builder, "Hello world",
521 "Roboto-Regular.ttf"));
522 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
523}
524
525TEST_P(AiksTest, DrawScaledTextWithPerspectiveSaveLayer) {
526 DisplayListBuilder builder;
527
528 Matrix matrix = Matrix(1.0, 0.0, 0.0, 0.0, //
529 0.0, 1.0, 0.0, 0.0, //
530 0.0, 0.0, 1.0, 0.01, //
531 0.0, 0.0, 0.0, 1.0) * //
533
534 DlPaint save_paint;
535 DlRect window_bounds =
536 DlRect::MakeXYWH(0, 0, GetWindowSize().width, GetWindowSize().height);
537 // Note: bounds were not needed by the AIKS version, which may indicate a bug.
538 builder.SaveLayer(window_bounds, &save_paint);
539 builder.Transform(matrix);
540
541 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), builder, "Hello world",
542 "Roboto-Regular.ttf"));
543
544 builder.Restore();
545 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
546}
547
548TEST_P(AiksTest, CanRenderTextWithLargePerspectiveTransform) {
549 // Verifies that text scales are clamped to work around
550 // https://github.com/flutter/flutter/issues/136112 .
551
552 DisplayListBuilder builder;
553
554 DlPaint save_paint;
555 builder.SaveLayer(std::nullopt, &save_paint);
556 builder.Transform(Matrix(2000, 0, 0, 0, //
557 0, 2000, 0, 0, //
558 0, 0, -1, 9000, //
559 0, 0, -1, 7000 //
560 ));
561
562 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), builder, "Hello world",
563 "Roboto-Regular.ttf"));
564 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
565}
566
567TEST_P(AiksTest, CanRenderTextWithPerspectiveTransformInSublist) {
568 DisplayListBuilder text_builder;
569 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), text_builder, "Hello world",
570 "Roboto-Regular.ttf"));
571 auto text_display_list = text_builder.Build();
572
573 DisplayListBuilder builder;
574
575 Matrix matrix = Matrix::MakeRow(2.0, 0.0, 0.0, 0.0, //
576 0.0, 2.0, 0.0, 0.0, //
577 0.0, 0.0, 1.0, 0.0, //
578 0.0, 0.002, 0.0, 1.0);
579
580 DlPaint save_paint;
581 DlRect window_bounds =
582 DlRect::MakeXYWH(0, 0, GetWindowSize().width, GetWindowSize().height);
583 builder.SaveLayer(window_bounds, &save_paint);
584 builder.Transform(matrix);
585 builder.DrawDisplayList(text_display_list, 1.0f);
586 builder.Restore();
587
588 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
589}
590
591// This currently renders solid blue, as the support for text color sources was
592// moved into DLDispatching. Path data requires the SkTextBlobs which are not
593// used in impeller::TextFrames.
594TEST_P(AiksTest, TextForegroundShaderWithTransform) {
595 auto mapping = flutter::testing::OpenFixtureAsSkData("Roboto-Regular.ttf");
596 ASSERT_NE(mapping, nullptr);
597
598 Scalar font_size = 100;
599 sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
600 SkFont sk_font(font_mgr->makeFromData(mapping), font_size);
601
602 DlPaint text_paint;
603 text_paint.setColor(DlColor::kBlue());
604
605 std::vector<DlColor> colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
606 DlColor::RGBA(0.1294, 0.5882, 0.9529, 1.0)};
607 std::vector<Scalar> stops = {
608 0.0,
609 1.0,
610 };
612 /*start_point=*/DlPoint(0, 0), //
613 /*end_point=*/DlPoint(100, 100), //
614 /*stop_count=*/2, //
615 /*colors=*/colors.data(), //
616 /*stops=*/stops.data(), //
617 /*tile_mode=*/DlTileMode::kRepeat //
618 ));
619
620 DisplayListBuilder builder;
621 builder.Translate(100, 100);
622 builder.Rotate(45);
623
624 auto blob = SkTextBlob::MakeFromString("Hello", sk_font);
625 ASSERT_NE(blob, nullptr);
626 auto frame = MakeTextFrameFromTextBlobSkia(blob);
627 builder.DrawText(DlTextImpeller::Make(frame), 0, 0, text_paint);
628
629 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
630}
631
632// Regression test for https://github.com/flutter/flutter/issues/157885.
633TEST_P(AiksTest, DifferenceClipsMustRenderIdenticallyAcrossBackends) {
634 DisplayListBuilder builder;
635
636 DlPaint paint;
637 DlColor clear_color(1.0, 0.5, 0.5, 0.5, DlColorSpace::kSRGB);
638 paint.setColor(clear_color);
639 builder.DrawPaint(paint);
640
641 DlMatrix identity = {
642 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
643 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
644 };
645 builder.Save();
646 builder.Transform(identity);
647
648 DlRect frame = DlRect::MakeLTRB(1.0, 1.0, 1278.0, 763.0);
649 DlColor white(1.0, 1.0, 1.0, 1.0, DlColorSpace::kSRGB);
650 paint.setColor(white);
651 builder.DrawRect(frame, paint);
652
653 builder.Save();
654 builder.ClipRect(frame, DlClipOp::kIntersect);
655
656 DlMatrix rect_xform = {
657 0.8241262, 0.56640625, 0.0, 0.0, -0.56640625, 0.8241262, 0.0, 0.0,
658 0.0, 0.0, 1.0, 0.0, 271.1137, 489.4733, 0.0, 1.0,
659 };
660 builder.Save();
661 builder.Transform(rect_xform);
662
663 DlRect rect = DlRect::MakeLTRB(0.0, 0.0, 100.0, 100.0);
664 DlColor bluish(1.0, 0.184, 0.501, 0.929, DlColorSpace::kSRGB);
665 paint.setColor(bluish);
666 DlRoundRect rrect = DlRoundRect::MakeRectRadius(rect, 18.0);
667 builder.DrawRoundRect(rrect, paint);
668
669 builder.Save();
670 builder.ClipRect(rect, DlClipOp::kIntersect);
671 builder.Restore();
672
673 builder.Restore();
674
675 DlMatrix path_xform = {
676 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
677 0.0, 0.0, 1.0, 0.0, 675.0, 279.5, 0.0, 1.0,
678 };
679 builder.Save();
680 builder.Transform(path_xform);
681
682 DlPathBuilder path_builder;
683 path_builder.MoveTo(DlPoint(87.5, 349.5));
684 path_builder.LineTo(DlPoint(25.0, 29.5));
685 path_builder.LineTo(DlPoint(150.0, 118.0));
686 path_builder.LineTo(DlPoint(25.0, 118.0));
687 path_builder.LineTo(DlPoint(150.0, 29.5));
688 path_builder.Close();
689 DlPath path = path_builder.TakePath();
690
691 DlColor fill_color(1.0, 1.0, 0.0, 0.0, DlColorSpace::kSRGB);
692 DlColor stroke_color(1.0, 0.0, 0.0, 0.0, DlColorSpace::kSRGB);
693 paint.setColor(fill_color);
694 paint.setDrawStyle(DlDrawStyle::kFill);
695 builder.DrawPath(path, paint);
696
697 paint.setColor(stroke_color);
698 paint.setStrokeWidth(2.0);
699 paint.setDrawStyle(DlDrawStyle::kStroke);
700 builder.DrawPath(path, paint);
701
702 builder.Restore();
703 builder.Restore();
704 builder.Restore();
705
706 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
707}
708
709TEST_P(AiksTest, TextContentsMismatchedTransformTest) {
710 AiksContext aiks_context(GetContext(),
711 std::make_shared<TypographerContextSkia>());
712
713 // Verifies that TextContents only use the scale/transform that is
714 // computed during preroll.
715 constexpr const char* font_fixture = "Roboto-Regular.ttf";
716
717 // Construct the text blob.
718 auto c_font_fixture = std::string(font_fixture);
719 auto mapping = flutter::testing::OpenFixtureAsSkData(c_font_fixture.c_str());
720 ASSERT_TRUE(mapping);
721
722 sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
723 SkFont sk_font(font_mgr->makeFromData(mapping), 16);
724
725 auto blob = SkTextBlob::MakeFromString("Hello World", sk_font);
726 ASSERT_TRUE(blob);
727
728 auto text_frame = MakeTextFrameFromTextBlobSkia(blob);
729
730 // Simulate recording the text frame during preroll.
731 Matrix preroll_matrix =
732 Matrix::MakeTranslateScale({1.5, 1.5, 1}, {100, 50, 0});
733 Point preroll_point = Point{23, 45};
734 {
736 (preroll_matrix * Matrix::MakeTranslation(preroll_point))
737 .GetMaxBasisLengthXY());
738
739 aiks_context.GetContentContext().GetLazyGlyphAtlas()->AddTextFrame(
740 text_frame, //
741 scale, //
742 preroll_point, //
743 preroll_matrix,
744 std::nullopt //
745 );
746 }
747
748 // Now simulate rendering with a slightly different scale factor.
749 RenderTarget render_target =
750 aiks_context.GetContentContext()
752 ->CreateOffscreenMSAA(*aiks_context.GetContext(), {100, 100}, 1);
753
754 TextContents text_contents;
755 text_contents.SetTextFrame(text_frame);
756 text_contents.SetOffset(preroll_point);
757 text_contents.SetScale(1.6);
758 text_contents.SetColor(Color::Aqua());
759
760 Matrix not_preroll_matrix =
761 Matrix::MakeTranslateScale({1.5, 1.5, 1}, {100, 50, 0});
762
763 Entity entity;
764 entity.SetTransform(not_preroll_matrix);
765
766 std::shared_ptr<CommandBuffer> command_buffer =
767 aiks_context.GetContext()->CreateCommandBuffer();
768 std::shared_ptr<RenderPass> render_pass =
769 command_buffer->CreateRenderPass(render_target);
770
771 EXPECT_TRUE(text_contents.Render(aiks_context.GetContentContext(), entity,
772 *render_pass));
773}
774
775TEST_P(AiksTest, TextWithShadowCache) {
776 DisplayListBuilder builder;
777 builder.Scale(GetContentScale().x, GetContentScale().y);
778 DlPaint paint;
779 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
780 builder.DrawPaint(paint);
781
782 AiksContext aiks_context(GetContext(),
783 std::make_shared<TypographerContextSkia>());
784 // Cache empty
785 EXPECT_EQ(aiks_context.GetContentContext()
788 0u);
789
790 ASSERT_TRUE(RenderTextInCanvasSkia(
791 GetContext(), builder, "Hello World", kFontFixture,
794 .filter = DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 4)}));
795
796 DisplayListToTexture(builder.Build(), {400, 400}, aiks_context);
797
798 // Text should be cached.
799 EXPECT_EQ(aiks_context.GetContentContext()
802 1u);
803}
804
805TEST_P(AiksTest, MultipleTextWithShadowCache) {
806 DisplayListBuilder builder;
807 builder.Scale(GetContentScale().x, GetContentScale().y);
808 DlPaint paint;
809 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
810 builder.DrawPaint(paint);
811
812 AiksContext aiks_context(GetContext(),
813 std::make_shared<TypographerContextSkia>());
814 // Cache empty
815 EXPECT_EQ(aiks_context.GetContentContext()
818 0u);
819
820 for (auto i = 0; i < 5; i++) {
821 ASSERT_TRUE(RenderTextInCanvasSkia(
822 GetContext(), builder, "Hello World", kFontFixture,
825 .filter = DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 4)}));
826 }
827
828 DisplayListToTexture(builder.Build(), {400, 400}, aiks_context);
829
830 // Text should be cached. Each text gets its own entry as we don't analyze the
831 // strings.
832 EXPECT_EQ(aiks_context.GetContentContext()
835 5u);
836}
837
838TEST_P(AiksTest, MultipleColorWithShadowCache) {
839 DisplayListBuilder builder;
840 builder.Scale(GetContentScale().x, GetContentScale().y);
841 DlPaint paint;
842 paint.setColor(DlColor::kWhite());
843 builder.DrawPaint(paint);
844
845 AiksContext aiks_context(GetContext(),
846 std::make_shared<TypographerContextSkia>());
847 // Cache empty
848 EXPECT_EQ(aiks_context.GetContentContext()
851 0u);
852
853 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
854
855 std::array<DlColor, 4> colors{DlColor::kRed(), DlColor::kGreen(),
857 for (const auto& color : colors) {
858 ASSERT_TRUE(RenderTextInCanvasSkia(
859 GetContext(), builder, "A", kFontFixture,
861 .color = color,
862 .filter = DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 4)},
863 sk_font));
864 }
865
866 DisplayListToTexture(builder.Build(), {400, 400}, aiks_context);
867
868 // The count of cache entries should match the number of distinct colors
869 // in the list. Repeated usage of a color should not add to the cache.
870 EXPECT_EQ(aiks_context.GetContentContext()
873 3u);
874}
875
876TEST_P(AiksTest, SingleIconShadowTest) {
877 DisplayListBuilder builder;
878 builder.Scale(GetContentScale().x, GetContentScale().y);
879 DlPaint paint;
880 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
881 builder.DrawPaint(paint);
882
883 AiksContext aiks_context(GetContext(),
884 std::make_shared<TypographerContextSkia>());
885 // Cache empty
886 EXPECT_EQ(aiks_context.GetContentContext()
889 0u);
890
891 // Create font instance outside loop so all draws use identical font instance.
892 auto c_font_fixture = std::string(kFontFixture);
893 auto mapping = flutter::testing::OpenFixtureAsSkData(c_font_fixture.c_str());
894 ASSERT_TRUE(mapping);
895 sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
896 SkFont sk_font(font_mgr->makeFromData(mapping), 50);
897
898 for (auto i = 0; i < 10; i++) {
899 ASSERT_TRUE(RenderTextInCanvasSkia(
900 GetContext(), builder, "A", kFontFixture,
903 .filter = DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 4)},
904 sk_font));
905 }
906
907 DisplayListToTexture(builder.Build(), {400, 400}, aiks_context);
908
909 // Text should be cached. All 10 glyphs use the same cache entry.
910 EXPECT_EQ(aiks_context.GetContentContext()
913 1u);
914}
915
916TEST_P(AiksTest, VarietyOfTextScalesShowingRasterAndPath) {
917 DisplayListBuilder builder;
918 DlPaint paint;
919 paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
920 builder.DrawPaint(paint);
921 builder.Scale(GetContentScale().x, GetContentScale().y);
922
923 std::vector<Scalar> scales = {4, 8, 16, 24, 32};
924 std::vector<Scalar> spacing = {8, 8, 8, 8, 8};
925 Scalar space = 16;
926 Scalar x = 0;
927 for (auto i = 0u; i < scales.size(); i++) {
928 builder.Save();
929 builder.Scale(scales[i], scales[i]);
931 GetContext(), builder, "lo", "Roboto-Regular.ttf",
932 TextRenderOptions{.font_size = 16, .position = DlPoint(x, space)});
933 space += spacing[i];
934 if (i == 3) {
935 x = 10;
936 space = 16;
937 }
938 builder.Restore();
939 }
940 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
941}
942
943} // namespace testing
944} // namespace impeller
void ClipRect(const DlRect &rect, DlClipOp clip_op=DlClipOp::kIntersect, bool is_aa=false) override
void DrawRoundRect(const DlRoundRect &rrect, const DlPaint &paint) override
void DrawCircle(const DlPoint &center, DlScalar radius, const DlPaint &paint) override
void SaveLayer(const std::optional< DlRect > &bounds, const DlPaint *paint=nullptr, const DlImageFilter *backdrop=nullptr, std::optional< int64_t > backdrop_id=std::nullopt) override
void Rotate(DlScalar degrees) override
void DrawText(const std::shared_ptr< DlText > &text, DlScalar x, DlScalar y, const DlPaint &paint) override
void Scale(DlScalar sx, DlScalar sy) override
void DrawDisplayList(const sk_sp< DisplayList > display_list, DlScalar opacity=SK_Scalar1) override
void Translate(DlScalar tx, DlScalar ty) override
void DrawPaint(const DlPaint &paint) override
sk_sp< DisplayList > Build()
Definition dl_builder.cc:66
void DrawPath(const DlPath &path, const DlPaint &paint) override
void Transform(const DlMatrix &matrix) override
void DrawRect(const DlRect &rect, const DlPaint &paint) override
static std::shared_ptr< DlMaskFilter > Make(DlBlurStyle style, SkScalar sigma, bool respect_ctm=true)
static std::shared_ptr< DlColorSource > MakeLinear(const DlPoint start_point, const DlPoint end_point, uint32_t stop_count, const DlColor *colors, const float *stops, DlTileMode tile_mode, const DlMatrix *matrix=nullptr)
DlPaint & setColor(DlColor color)
Definition dl_paint.h:70
DlPaint & setStrokeWidth(float width)
Definition dl_paint.h:115
DlPaint & setBlendMode(DlBlendMode mode)
Definition dl_paint.h:85
DlPaint & setMaskFilter(std::nullptr_t filter)
Definition dl_paint.h:185
DlPaint & setDrawStyle(DlDrawStyle style)
Definition dl_paint.h:93
DlPaint & setColorSource(std::nullptr_t source)
Definition dl_paint.h:131
DlPathBuilder & LineTo(DlPoint p2)
Draw a line from the current point to the indicated point p2.
DlPathBuilder & MoveTo(DlPoint p2)
Start a new contour that will originate at the indicated point p2.
const DlPath TakePath()
Returns the path constructed by this path builder and resets its internal state to the default state ...
DlPathBuilder & Close()
The path is closed back to the location of the most recent MoveTo call. Contours that are filled are ...
static std::shared_ptr< DlTextImpeller > Make(const std::shared_ptr< impeller::TextFrame > &frame)
ContentContext & GetContentContext() const
std::shared_ptr< Context > GetContext() const
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
const std::shared_ptr< RenderTargetAllocator > & GetRenderTargetCache() const
const std::shared_ptr< LazyGlyphAtlas > & GetLazyGlyphAtlas() const
TextShadowCache & GetTextShadowCache() const
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition entity.cc:60
void SetOffset(Vector2 offset)
bool Render(const ContentContext &renderer, const Entity &entity, RenderPass &pass) const override
void SetTextFrame(const std::shared_ptr< TextFrame > &frame)
void SetScale(Scalar scale)
void SetColor(Color color)
static Rational RoundScaledFontSize(Scalar scale)
Definition text_frame.cc:55
size_t GetCacheSizeForTesting() const
int32_t x
FlutterDesktopBinaryReply callback
std::u16string text
double y
SkFont CreateTestFontOfSize(DlScalar scalar)
sk_sp< SkData > OpenFixtureAsSkData(const std::string &fixture_name)
Opens a fixture of the given file name and returns a Skia SkData holding its contents.
Definition testing.cc:63
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switch_defs.h:52
DlDrawStyle
Definition dl_paint.h:19
@ kFill
fills interior of shapes
impeller::Point DlPoint
bool RenderTextInCanvasSkia(const std::shared_ptr< Context > &context, DisplayListBuilder &canvas, const std::string &text, const std::string_view &font_fixture, const TextRenderOptions &options={}, const std::optional< SkFont > &font=std::nullopt)
TEST_P(AiksTest, DrawAtlasNoColor)
static constexpr std::string_view kFontFixture
constexpr float k2Pi
Definition constants.h:29
std::shared_ptr< Texture > DisplayListToTexture(const sk_sp< flutter::DisplayList > &display_list, ISize size, AiksContext &context, bool reset_host_buffer, bool generate_mips)
Render the provided display list to a texture with the given size.
float Scalar
Definition scalar.h:19
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
sk_sp< SkFontMgr > GetDefaultFontManager(uint32_t font_initialization_data)
Definition platform.cc:17
int32_t height
int32_t width
static constexpr DlColor kWhite()
Definition dl_color.h:70
static constexpr DlColor kBlue()
Definition dl_color.h:73
static constexpr DlColor RGBA(DlScalar r, DlScalar g, DlScalar b, DlScalar a)
Construct a 32 bit color from floating point R, G, B, and A color channels.
Definition dl_color.h:48
static constexpr DlColor kBlack()
Definition dl_color.h:69
static constexpr DlColor ARGB(DlScalar a, DlScalar r, DlScalar g, DlScalar b)
Construct a 32 bit color from floating point A, R, G, and B color channels.
Definition dl_color.h:57
static constexpr DlColor kAqua()
Definition dl_color.h:86
static constexpr DlColor kYellow()
Definition dl_color.h:76
static constexpr DlColor kRed()
Definition dl_color.h:71
static constexpr DlColor kGreen()
Definition dl_color.h:72
static constexpr DlColor kOrange()
Definition dl_color.h:87
constexpr DlColor modulateOpacity(DlScalar opacity) const
Definition dl_color.h:150
static constexpr Color Aqua()
Definition color.h:290
A 4x4 matrix using column-major storage.
Definition matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition matrix.h:95
static Matrix MakeRotationY(Radians r)
Definition matrix.h:208
static constexpr Matrix MakeRow(Scalar m0, Scalar m1, Scalar m2, Scalar m3, Scalar m4, Scalar m5, Scalar m6, Scalar m7, Scalar m8, Scalar m9, Scalar m10, Scalar m11, Scalar m12, Scalar m13, Scalar m14, Scalar m15)
Definition matrix.h:83
static constexpr Matrix MakeTranslateScale(const Vector3 &s, const Vector3 &t)
Definition matrix.h:113
static RoundRect MakeRectRadius(const Rect &rect, Scalar radius)
Definition round_rect.h:27
static constexpr TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition rect.h:136
static constexpr TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition rect.h:129
std::shared_ptr< DlMaskFilter > filter
Scalar font_size
bool is_subpixel