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