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