Flutter Impeller
text_contents_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 
6 #include "flutter/impeller/renderer/testing/mocks.h"
7 #include "flutter/testing/testing.h"
12 #include "third_party/googletest/googletest/include/gtest/gtest.h"
13 #include "txt/platform.h"
14 
15 #pragma GCC diagnostic ignored "-Wunreachable-code"
16 
17 namespace impeller {
18 namespace testing {
19 
22 
23 using ::testing::Return;
24 
25 namespace {
26 struct TextOptions {
28  bool is_subpixel = false;
29 };
30 
31 std::shared_ptr<TextFrame> MakeTextFrame(const std::string& text,
32  const std::string_view& font_fixture,
33  const TextOptions& options) {
34  auto c_font_fixture = std::string(font_fixture);
35  auto mapping = flutter::testing::OpenFixtureAsSkData(c_font_fixture.c_str());
36  if (!mapping) {
37  return nullptr;
38  }
39  sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
40  SkFont sk_font(font_mgr->makeFromData(mapping), options.font_size);
41  if (options.is_subpixel) {
42  sk_font.setSubpixel(true);
43  }
44  auto blob = SkTextBlob::MakeFromString(text.c_str(), sk_font);
45  if (!blob) {
46  return nullptr;
47  }
48 
49  return MakeTextFrameFromTextBlobSkia(blob);
50 }
51 
52 std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
53  Context& context,
54  const TypographerContext* typographer_context,
55  HostBuffer& data_host_buffer,
56  GlyphAtlas::Type type,
57  const Matrix& transform,
58  const std::shared_ptr<GlyphAtlasContext>& atlas_context,
59  const std::shared_ptr<TextFrame>& frame,
60  Point offset) {
61  RenderableText render_frame{
62  .text_frame = frame,
63  .origin_transform = transform * Matrix::MakeTranslation(offset),
64  };
65  return typographer_context->CreateGlyphAtlas(context, type, data_host_buffer,
66  atlas_context, {render_frame});
67 }
68 
69 Rect PerVertexDataPositionToRect(
70  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData>::iterator
71  data) {
72  Scalar right = FLT_MIN;
73  Scalar left = FLT_MAX;
74  Scalar top = FLT_MAX;
75  Scalar bottom = FLT_MIN;
76  for (int i = 0; i < 4; ++i) {
77  right = std::max(right, data[i].position.x);
78  left = std::min(left, data[i].position.x);
79  top = std::min(top, data[i].position.y);
80  bottom = std::max(bottom, data[i].position.y);
81  }
82 
83  return Rect::MakeLTRB(left, top, right, bottom);
84 }
85 
86 Rect PerVertexDataUVToRect(
87  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData>::iterator data,
88  ISize texture_size) {
89  Scalar right = FLT_MIN;
90  Scalar left = FLT_MAX;
91  Scalar top = FLT_MAX;
92  Scalar bottom = FLT_MIN;
93  for (int i = 0; i < 4; ++i) {
94  right = std::max(right, data[i].uv.x * texture_size.width);
95  left = std::min(left, data[i].uv.x * texture_size.width);
96  top = std::min(top, data[i].uv.y * texture_size.height);
97  bottom = std::max(bottom, data[i].uv.y * texture_size.height);
98  }
99 
100  return Rect::MakeLTRB(left, top, right, bottom);
101 }
102 
103 double GetAspectRatio(Rect rect) {
104  return static_cast<double>(rect.GetWidth()) / rect.GetHeight();
105 }
106 } // namespace
107 
108 TEST_P(TextContentsTest, SimpleComputeVertexData) {
109 #ifndef FML_OS_MACOSX
110  GTEST_SKIP() << "Results aren't stable across linux and macos.";
111 #endif
112 
113  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
114 
115  std::shared_ptr<TextFrame> text_frame =
116  MakeTextFrame("1", "ahem.ttf", TextOptions{.font_size = 50});
117 
118  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
119  std::shared_ptr<GlyphAtlasContext> atlas_context =
120  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
121  std::shared_ptr<HostBuffer> data_host_buffer = HostBuffer::Create(
122  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
123  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
124  ASSERT_TRUE(context && context->IsValid());
125  std::shared_ptr<GlyphAtlas> atlas =
126  CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
127  GlyphAtlas::Type::kAlphaBitmap, Matrix(), atlas_context,
128  text_frame, /*offset=*/{0, 0});
129 
130  ISize texture_size = atlas->GetTexture()->GetSize();
132  /*entity_transform=*/Matrix(),
133  /*frame=*/text_frame,
134  /*position=*/Point(0, 0),
135  /*screen_transform=*/Matrix(),
136  /*glyph_properties=*/std::nullopt,
137  /*atlas=*/atlas);
138 
139  Rect position_rect = PerVertexDataPositionToRect(data.begin());
140  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
141  // The -1 offset comes from Skia in `ComputeGlyphSize`. So since the font size
142  // is 50, the math appears to be to get back a 50x50 rect and apply 1 pixel
143  // of padding.
144  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
145  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
146 }
147 
148 TEST_P(TextContentsTest, SimpleComputeVertexData2x) {
149 #ifndef FML_OS_MACOSX
150  GTEST_SKIP() << "Results aren't stable across linux and macos.";
151 #endif
152 
153  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
154  std::shared_ptr<TextFrame> text_frame =
155  MakeTextFrame("1", "ahem.ttf", TextOptions{.font_size = 50});
156 
157  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
158  std::shared_ptr<GlyphAtlasContext> atlas_context =
159  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
160  std::shared_ptr<HostBuffer> data_host_buffer = HostBuffer::Create(
161  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
162  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
163  ASSERT_TRUE(context && context->IsValid());
164  Matrix render_transform = Matrix::MakeScale({2.0f, 2.0f, 1.0f});
165  std::shared_ptr<GlyphAtlas> atlas =
166  CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
167  GlyphAtlas::Type::kAlphaBitmap, render_transform,
168  atlas_context, text_frame, /*offset=*/{0, 0});
169 
170  ISize texture_size = atlas->GetTexture()->GetSize();
172  /*vtx_contents=*/data.data(),
173  /*entity_transform=*/render_transform,
174  /*frame=*/text_frame,
175  /*position=*/Point(0, 0),
176  /*screen_transform=*/render_transform,
177  /*glyph_properties=*/std::nullopt,
178  /*atlas=*/atlas);
179 
180  Rect position_rect = PerVertexDataPositionToRect(data.begin());
181  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
182  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -81, 102, 102));
183  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 102, 102));
184 }
185 
186 TEST_P(TextContentsTest, MaintainsShape) {
187  std::shared_ptr<TextFrame> text_frame =
188  MakeTextFrame("th", "ahem.ttf", TextOptions{.font_size = 50});
189 
190  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
191  std::shared_ptr<GlyphAtlasContext> atlas_context =
192  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
193  std::shared_ptr<HostBuffer> data_host_buffer = HostBuffer::Create(
194  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
195  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
196  ASSERT_TRUE(context && context->IsValid());
197 
198  for (int i = 0; i <= 1000; ++i) {
199  Scalar scale = (440.0f + i) / 1000.0f;
200  Matrix transform = Matrix::MakeScale({scale, scale, 1.0f});
201  Rect position_rect[2];
202  Rect uv_rect[2];
203 
204  {
205  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(12);
206 
207  std::shared_ptr<GlyphAtlas> atlas =
208  CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
210  atlas_context, text_frame, /*offset=*/{0, 0});
211  ISize texture_size = atlas->GetTexture()->GetSize();
212 
214  /*vtx_contents=*/data.data(),
215  /*entity_transform=*/transform,
216  /*frame=*/text_frame,
217  /*position=*/Point(0, 0),
218  /*screen_transform=*/transform,
219  /*glyph_properties=*/std::nullopt,
220  /*atlas=*/atlas);
221  position_rect[0] = PerVertexDataPositionToRect(data.begin());
222  uv_rect[0] = PerVertexDataUVToRect(data.begin(), texture_size);
223  position_rect[1] = PerVertexDataPositionToRect(data.begin() + 4);
224  uv_rect[1] = PerVertexDataUVToRect(data.begin() + 4, texture_size);
225  }
226  EXPECT_NEAR(GetAspectRatio(position_rect[1]), GetAspectRatio(uv_rect[1]),
227  0.001)
228  << i;
229  }
230 }
231 
232 TEST_P(TextContentsTest, SimpleSubpixel) {
233 #ifndef FML_OS_MACOSX
234  GTEST_SKIP() << "Results aren't stable across linux and macos.";
235 #endif
236 
237  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
238 
239  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
240  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
241 
242  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
243  std::shared_ptr<GlyphAtlasContext> atlas_context =
244  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
245  std::shared_ptr<HostBuffer> data_host_buffer = HostBuffer::Create(
246  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
247  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
248  ASSERT_TRUE(context && context->IsValid());
249  Point offset = Point(0.5, 0);
250  std::shared_ptr<GlyphAtlas> atlas =
251  CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
252  GlyphAtlas::Type::kAlphaBitmap, Matrix(), atlas_context,
253  text_frame, offset);
254 
255  ISize texture_size = atlas->GetTexture()->GetSize();
257  /*vtx_contents=*/data.data(),
258  /*entity_transform=*/Matrix(),
259  /*frame=*/text_frame,
260  /*position=*/offset,
261  /*screen_transform=*/Matrix(),
262  /*glyph_properties=*/std::nullopt,
263  /*atlas=*/atlas);
264 
265  Rect position_rect = PerVertexDataPositionToRect(data.begin());
266  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
267  // The values at Point(0, 0).
268  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
269  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
270  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -41, 54, 52));
271  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 54, 52));
272 }
273 
274 TEST_P(TextContentsTest, SimpleSubpixel3x) {
275 #ifndef FML_OS_MACOSX
276  GTEST_SKIP() << "Results aren't stable across linux and macos.";
277 #endif
278 
279  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
280 
281  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
282  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
283 
284  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
285  std::shared_ptr<GlyphAtlasContext> atlas_context =
286  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
287  std::shared_ptr<HostBuffer> data_host_buffer = HostBuffer::Create(
288  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
289  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
290  ASSERT_TRUE(context && context->IsValid());
291  Matrix transform = Matrix::MakeScale({3.0f, 3.0f, 1.0f});
292  Point offset = {0.16667, 0};
293  std::shared_ptr<GlyphAtlas> atlas =
294  CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
296  text_frame, offset);
297 
298  ISize texture_size = atlas->GetTexture()->GetSize();
300  /*vtx_contents=*/data.data(),
301  /*entity_transform=*/transform,
302  /*frame=*/text_frame,
303  /*position=*/offset,
304  /*screen_transform=*/transform,
305  /*glyph_properties=*/std::nullopt,
306  /*atlas=*/atlas);
307 
308  Rect position_rect = PerVertexDataPositionToRect(data.begin());
309  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
310  // Values at Point(0, 0)
311  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -121, 152, 152));
312  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 152, 152));
313  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -121, 154, 152))
314  << "position size:" << position_rect.GetSize();
315  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 154, 152))
316  << "position size:" << position_rect.GetSize();
317 }
318 
319 TEST_P(TextContentsTest, SimpleSubpixel26) {
320 #ifndef FML_OS_MACOSX
321  GTEST_SKIP() << "Results aren't stable across linux and macos.";
322 #endif
323 
324  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
325 
326  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
327  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
328 
329  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
330  std::shared_ptr<GlyphAtlasContext> atlas_context =
331  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
332  std::shared_ptr<HostBuffer> data_host_buffer = HostBuffer::Create(
333  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
334  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
335  ASSERT_TRUE(context && context->IsValid());
336  Point offset = Point(0.26, 0);
337  std::shared_ptr<GlyphAtlas> atlas =
338  CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
339  GlyphAtlas::Type::kAlphaBitmap, Matrix(), atlas_context,
340  text_frame, offset);
341 
342  ISize texture_size = atlas->GetTexture()->GetSize();
344  /*vtx_contents=*/data.data(),
345  /*entity_transform=*/Matrix(),
346  /*frame=*/text_frame,
347  /*position=*/offset,
348  /*screen_transform=*/Matrix(),
349  /*glyph_properties=*/std::nullopt,
350  /*atlas=*/atlas);
351 
352  Rect position_rect = PerVertexDataPositionToRect(data.begin());
353  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
354  // The values without subpixel.
355  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
356  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
357  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -41, 54, 52));
358  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 54, 52));
359 }
360 
361 TEST_P(TextContentsTest, SimpleSubpixel80) {
362 #ifndef FML_OS_MACOSX
363  GTEST_SKIP() << "Results aren't stable across linux and macos.";
364 #endif
365 
366  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
367 
368  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
369  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
370 
371  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
372  std::shared_ptr<GlyphAtlasContext> atlas_context =
373  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
374  std::shared_ptr<HostBuffer> data_host_buffer = HostBuffer::Create(
375  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
376  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
377  ASSERT_TRUE(context && context->IsValid());
378  Point offset = Point(0.80, 0);
379  std::shared_ptr<GlyphAtlas> atlas =
380  CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
381  GlyphAtlas::Type::kAlphaBitmap, Matrix(), atlas_context,
382  text_frame, offset);
383 
384  ISize texture_size = atlas->GetTexture()->GetSize();
386  /*vtx_contents=*/data.data(),
387  /*entity_transform=*/Matrix(),
388  /*frame=*/text_frame,
389  /*position=*/offset,
390  /*screen_transform=*/Matrix(),
391  /*glyph_properties=*/std::nullopt,
392  /*atlas=*/atlas);
393 
394  Rect position_rect = PerVertexDataPositionToRect(data.begin());
395  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
396  // The values without subpixel.
397  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
398  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
399  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -41, 54, 52));
400  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 54, 52));
401 }
402 
403 } // namespace testing
404 } // namespace impeller
Type
Describes how the glyphs are represented in the texture.
Definition: glyph_atlas.h:41
static std::shared_ptr< HostBuffer > Create(const std::shared_ptr< Allocator > &allocator, const std::shared_ptr< const IdleWaiter > &idle_waiter, size_t minimum_uniform_alignment)
Definition: host_buffer.cc:21
static void ComputeVertexData(GlyphAtlasPipeline::VertexShader::PerVertexData *vtx_contents, const Matrix &entity_transform, const std::shared_ptr< TextFrame > &frame, Point position, const Matrix &screen_transform, std::optional< GlyphProperties > glyph_properties, const std::shared_ptr< GlyphAtlas > &atlas)
Computes the vertex data for the render operation from a collection of data drawn from the DrawTextFr...
static std::shared_ptr< TypographerContext > Make()
#define EXPECT_RECT_NEAR(a, b)
TEST_P(AiksTest, DrawAtlasNoColor)
static std::shared_ptr< GlyphAtlas > CreateGlyphAtlas(Context &context, const TypographerContext *typographer_context, HostBuffer &data_host_buffer, GlyphAtlas::Type type, const Matrix &transform, const std::shared_ptr< GlyphAtlasContext > &atlas_context, const std::shared_ptr< TextFrame > &frame)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
float Scalar
Definition: scalar.h:19
TRect< Scalar > Rect
Definition: rect.h:788
TPoint< Scalar > Point
Definition: point.h:426
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
ISize64 ISize
Definition: size.h:162
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
constexpr TSize< Type > GetSize() const
Returns the size of the rectangle which may be negative in either width or height and may have been c...
Definition: rect.h:327
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
Scalar font_size
bool is_subpixel