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