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