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