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