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