| Flutter Impeller |

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
5 #include "flutter/display_list/display_list.h"
6 #include "flutter/display_list/dl_blend_mode.h"
7 #include "flutter/display_list/dl_builder.h"
8 #include "flutter/display_list/dl_color.h"
9 #include "flutter/display_list/dl_paint.h"
10 #include "flutter/display_list/dl_tile_mode.h"
11 #include "flutter/display_list/effects/dl_color_source.h"
12 #include "flutter/display_list/effects/dl_mask_filter.h"
13 #include "flutter/display_list/geometry/dl_path_builder.h"
14 #include "flutter/display_list/testing/dl_test_snippets.h"
15 #include "flutter/fml/build_config.h"
16 #include "flutter/impeller/display_list/aiks_unittests.h"
17 #include "flutter/testing/testing.h"
18 #include "impeller/display_list/aiks_context.h"
19 #include "impeller/display_list/dl_dispatcher.h"
20 #include "impeller/display_list/dl_text_impeller.h"
21 #include "impeller/entity/contents/content_context.h"
22 #include "impeller/entity/contents/text_contents.h"
23 #include "impeller/entity/entity.h"
24 #include "impeller/geometry/matrix.h"
25 #include "impeller/typographer/backends/skia/text_frame_skia.h"
26
27 #include "impeller/typographer/backends/skia/typographer_context_skia.h"
28 #include "txt/platform.h"
29
30 using namespace flutter;
31 /////////////////////////////////////////////////////
32
33 namespace impeller {
34 namespace testing {
35
36 struct TextRenderOptions {
37 bool stroke = false;
38 Scalar font_size = 50;
39 Scalar stroke_width = 1;
40 DlColor color = DlColor::kYellow();
41 DlPoint position = DlPoint(100, 200);
42 std::shared_ptr<DlMaskFilter> filter;
43 bool is_subpixel = false;
44 };
45
46 bool 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
93 : DlDrawStyle::kFill);
94 canvas.DrawText(DlTextImpeller::Make(frame), options.position.x,
95 options.position.y, text_paint);
96 return true;
97 }
98
99 TEST_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
112 TEST_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
128 TEST_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
144 TEST_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
160 TEST_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.
175 TEST_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);
185 RenderTextInCanvasSkia(
186 GetContext(), builder, "k", "Roboto-Regular.ttf",
187 TextRenderOptions{.font_size = 600, .position = DlPoint(10, 500)});
188 RenderTextInCanvasSkia(
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.
197 TEST_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);
212 RenderTextInCanvasSkia(
213 GetContext(), builder, "HELLO", "Roboto-Regular.ttf",
214 TextRenderOptions{.font_size = 16,
215 .color = (16 * scale >= 250) ? DlColor::kYellow()
216 : DlColor::kOrange(),
217 .position = DlPoint(0, 20)});
218 return builder.Build();
219 };
220
221 ASSERT_TRUE(OpenPlaygroundHere(callback));
222 }
223
224 TEST_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
243 RenderTextInCanvasSkia(
244 GetContext(), builder, "test", "Roboto-Regular.ttf",
245 TextRenderOptions{
246 .position = DlPoint(offsetx / scale, offsety / scale),
247 });
248 return builder.Build();
249 };
250 ASSERT_TRUE(OpenPlaygroundHere(callback));
251 }
252
253 TEST_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",
273 TextRenderOptions{.is_subpixel = is_subpixel});
274 return builder.Build();
275 };
276
277 ASSERT_TRUE(OpenPlaygroundHere(callback));
278 }
279
280 // https://github.com/flutter/flutter/issues/164958
281 TEST_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",
307 TextRenderOptions{
308 .color = DlColor::kBlack(),
309 .position = DlPoint(foffset[0], foffset[1]),
310 });
311
312 builder.Restore();
313 return builder.Build();
314 };
315 ASSERT_TRUE(OpenPlaygroundHere(callback));
316 }
317
318 TEST_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 );
349 if (!RenderTextInCanvasSkia(
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
364 TEST_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
377 static constexpr std::string_view kFontFixture =
378 #if FML_OS_MACOSX
379 "Apple Color Emoji.ttc";
380 #else
381 "NotoColorEmoji.ttf";
382 #endif
383
384 TEST_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
396 TEST_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,
406 TextRenderOptions{
407 .color = DlColor::kBlue(),
408 .filter = DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 4)}));
409 ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
410 }
411
412 TEST_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
425 TEST_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
451 TEST_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
490 TEST_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
509 TEST_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) * //
516 Matrix::MakeRotationY({Degrees{10}});
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
525 TEST_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) * //
532 Matrix::MakeRotationY({Degrees{10}});
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
548 TEST_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
567 TEST_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.
594 TEST_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 };
611 text_paint.setColorSource(DlColorSource::MakeLinear(
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.
633 TEST_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
709 TEST_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 {
735 auto scale = TextFrame::RoundScaledFontSize(
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()
751 .GetRenderTargetCache()
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
775 TEST_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()
786 .GetTextShadowCache()
787 .GetCacheSizeForTesting(),
788 0u);
789
790 ASSERT_TRUE(RenderTextInCanvasSkia(
791 GetContext(), builder, "Hello World", kFontFixture,
792 TextRenderOptions{
793 .color = DlColor::kBlue(),
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()
800 .GetTextShadowCache()
801 .GetCacheSizeForTesting(),
802 1u);
803 }
804
805 TEST_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()
816 .GetTextShadowCache()
817 .GetCacheSizeForTesting(),
818 0u);
819
820 for (auto i = 0; i < 5; i++) {
821 ASSERT_TRUE(RenderTextInCanvasSkia(
822 GetContext(), builder, "Hello World", kFontFixture,
823 TextRenderOptions{
824 .color = DlColor::kBlue(),
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()
833 .GetTextShadowCache()
834 .GetCacheSizeForTesting(),
835 5u);
836 }
837
838 TEST_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()
849 .GetTextShadowCache()
850 .GetCacheSizeForTesting(),
851 0u);
852
853 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
854
855 std::array<DlColor, 4> colors{DlColor::kRed(), DlColor::kGreen(),
856 DlColor::kBlue(), DlColor::kRed()};
857 for (const auto& color : colors) {
858 ASSERT_TRUE(RenderTextInCanvasSkia(
859 GetContext(), builder, "A", kFontFixture,
860 TextRenderOptions{
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()
871 .GetTextShadowCache()
872 .GetCacheSizeForTesting(),
873 3u);
874 }
875
876 TEST_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()
887 .GetTextShadowCache()
888 .GetCacheSizeForTesting(),
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,
901 TextRenderOptions{
902 .color = DlColor::kBlue(),
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()
911 .GetTextShadowCache()
912 .GetCacheSizeForTesting(),
913 1u);
914 }
915
916 TEST_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]);
930 RenderTextInCanvasSkia(
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
aiks_context.h
aiks_unittests.h
flutter::DlTextImpeller::Make
static std::shared_ptr< DlTextImpeller > Make(const std::shared_ptr< impeller::TextFrame > &frame)
Definition: dl_text_impeller.cc:11
impeller::AiksContext
Definition: aiks_context.h:20
impeller::AiksContext::GetContentContext
ContentContext & GetContentContext() const
Definition: aiks_context.cc:42
impeller::AiksContext::GetContext
std::shared_ptr< Context > GetContext() const
Definition: aiks_context.cc:38
impeller::AiksPlayground
Definition: aiks_playground.h:16
impeller::ContentContext::GetLazyGlyphAtlas
const std::shared_ptr< LazyGlyphAtlas > & GetLazyGlyphAtlas() const
Definition: content_context.h:266
impeller::ContentContext::GetRenderTargetCache
const std::shared_ptr< RenderTargetAllocator > & GetRenderTargetCache() const
Definition: content_context.h:270
impeller::ContentContext::GetTextShadowCache
TextShadowCache & GetTextShadowCache() const
Definition: content_context.h:316
impeller::Entity
Definition: entity.h:26
impeller::Entity::SetTransform
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:62
impeller::RenderTarget
Definition: render_target.h:38
impeller::TextContents
Definition: text_contents.h:23
impeller::TextContents::SetOffset
void SetOffset(Vector2 offset)
Definition: text_contents.cc:48
impeller::TextContents::Render
bool Render(const ContentContext &renderer, const Entity &entity, RenderPass &pass) const override
Definition: text_contents.cc:220
impeller::TextContents::SetTextFrame
void SetTextFrame(const std::shared_ptr< TextFrame > &frame)
Definition: text_contents.cc:32
impeller::TextContents::SetScale
void SetScale(Scalar scale)
Definition: text_contents.h:56
impeller::TextContents::SetColor
void SetColor(Color color)
Definition: text_contents.cc:36
impeller::TextShadowCache::GetCacheSizeForTesting
size_t GetCacheSizeForTesting() const
Definition: text_shadow_cache.h:90
content_context.h
dl_dispatcher.h
x
int32_t x
Definition: dl_golden_unittests.cc:458
dl_text_impeller.h
entity.h
matrix.h
flutter
Definition: dl_golden_blur_unittests.cc:16
impeller::testing::RenderTextInCanvasSkia
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)
Definition: aiks_dl_text_unittests.cc:46
impeller::testing::TEST_P
TEST_P(AiksTest, VarietyOfTextScalesShowingRasterAndPath)
Definition: aiks_dl_text_unittests.cc:916
impeller::testing::kFontFixture
static constexpr std::string_view kFontFixture
Definition: aiks_dl_text_unittests.cc:377
impeller
Definition: allocation.cc:12
impeller::k2Pi
constexpr float k2Pi
Definition: constants.h:29
impeller::DlRect
flutter::DlRect DlRect
Definition: dl_dispatcher.h:25
impeller::Scalar
float Scalar
Definition: scalar.h:19
impeller::DisplayListToTexture
std::shared_ptr< Texture > DisplayListToTexture(const sk_sp< flutter::DisplayList > &display_list, ISize size, AiksContext &context, bool reset_host_buffer, bool generate_mips, std::optional< PixelFormat > target_pixel_format)
Render the provided display list to a texture with the given size.
Definition: dl_dispatcher.cc:1220
impeller::DlRoundRect
flutter::DlRoundRect DlRoundRect
Definition: dl_dispatcher.h:27
impeller::Point
TPoint< Scalar > Point
Definition: point.h:327
impeller::DlPoint
flutter::DlPoint DlPoint
Definition: dl_dispatcher.h:24
impeller::DlPath
flutter::DlPath DlPath
Definition: dl_dispatcher.h:29
impeller::MakeTextFrameFromTextBlobSkia
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
Definition: text_frame_skia.cc:45
impeller::Degrees
Definition: scalar.h:66
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::Matrix::Transform
constexpr Quad Transform(const Quad &quad) const
Definition: matrix.h:623
impeller::TPoint< Scalar >
impeller::testing::TextRenderOptions
Definition: aiks_dl_text_unittests.cc:36
impeller::testing::TextRenderOptions::filter
std::shared_ptr< DlMaskFilter > filter
Definition: aiks_dl_text_unittests.cc:42
impeller::testing::TextRenderOptions::color
DlColor color
Definition: aiks_dl_text_unittests.cc:40
impeller::testing::TextRenderOptions::font_size
Scalar font_size
Definition: aiks_dl_text_unittests.cc:38
impeller::testing::TextRenderOptions::position
DlPoint position
Definition: aiks_dl_text_unittests.cc:41
impeller::testing::TextRenderOptions::is_subpixel
bool is_subpixel
Definition: aiks_dl_text_unittests.cc:43
text_contents.h
font_size
Scalar font_size
Definition: text_contents_unittests.cc:27
is_subpixel
bool is_subpixel
Definition: text_contents_unittests.cc:28
text_frame_skia.h
typographer_context_skia.h