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"
15 #include "flutter/testing/testing.h"
20 #include "impeller/entity/entity.h"
23 
25 #include "txt/platform.h"
26 
27 using namespace flutter;
28 /////////////////////////////////////////////////////
29 
30 namespace impeller {
31 namespace testing {
32 
34  bool stroke = false;
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);
183  GetContext(), builder, "k", "Roboto-Regular.ttf",
184  TextRenderOptions{.font_size = 600, .position = DlPoint(10, 500)});
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);
210  GetContext(), builder, "HELLO", "Roboto-Regular.ttf",
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 
241  GetContext(), builder, "test", "Roboto-Regular.ttf",
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",
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",
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  );
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,
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()
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()
785  0u);
786 
787  ASSERT_TRUE(RenderTextInCanvasSkia(
788  GetContext(), builder, "Hello World", kFontFixture,
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()
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()
815  0u);
816 
817  for (auto i = 0; i < 5; i++) {
818  ASSERT_TRUE(RenderTextInCanvasSkia(
819  GetContext(), builder, "Hello World", kFontFixture,
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()
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()
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,
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()
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]);
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
ContentContext & GetContentContext() const
Definition: aiks_context.cc:42
std::shared_ptr< Context > GetContext() const
Definition: aiks_context.cc:38
const std::shared_ptr< LazyGlyphAtlas > & GetLazyGlyphAtlas() const
const std::shared_ptr< RenderTargetAllocator > & GetRenderTargetCache() const
TextShadowCache & GetTextShadowCache() const
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:60
void SetOffset(Vector2 offset)
bool Render(const ContentContext &renderer, const Entity &entity, RenderPass &pass) const override
void SetTextFrame(const std::shared_ptr< TextFrame > &frame)
void SetScale(Scalar scale)
Definition: text_contents.h:59
void SetColor(Color color)
size_t GetCacheSizeForTesting() const
int32_t x
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)
TEST_P(AiksTest, VarietyOfTextScalesShowingRasterAndPath)
static constexpr std::string_view kFontFixture
constexpr float k2Pi
Definition: constants.h:29
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.
flutter::DlRect DlRect
Definition: dl_dispatcher.h:25
float Scalar
Definition: scalar.h:18
flutter::DlRoundRect DlRoundRect
Definition: dl_dispatcher.h:27
TPoint< Scalar > Point
Definition: point.h:327
flutter::DlPoint DlPoint
Definition: dl_dispatcher.h:24
flutter::DlPath DlPath
Definition: dl_dispatcher.h:29
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
const Scalar stroke_width
const Scalar scale
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
constexpr Quad Transform(const Quad &quad) const
Definition: matrix.h:556
std::shared_ptr< DlMaskFilter > filter
Scalar font_size
bool is_subpixel