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& host_buffer,
57  Rational scale,
58  const std::shared_ptr<GlyphAtlasContext>& atlas_context,
59  const std::shared_ptr<TextFrame>& frame,
60  Point offset) {
61  frame->SetPerFrameData(
62  TextFrame::RoundScaledFontSize(scale), /*offset=*/offset,
63  /*transform=*/
65  Vector3{static_cast<Scalar>(scale), static_cast<Scalar>(scale), 1}),
66  /*properties=*/std::nullopt);
67  return typographer_context->CreateGlyphAtlas(context, type, host_buffer,
68  atlas_context, {frame});
69 }
70 
71 Rect PerVertexDataPositionToRect(
72  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData>::iterator
73  data) {
74  Scalar right = FLT_MIN;
75  Scalar left = FLT_MAX;
76  Scalar top = FLT_MAX;
77  Scalar bottom = FLT_MIN;
78  for (int i = 0; i < 4; ++i) {
79  right = std::max(right, data[i].position.x);
80  left = std::min(left, data[i].position.x);
81  top = std::min(top, data[i].position.y);
82  bottom = std::max(bottom, data[i].position.y);
83  }
84 
85  return Rect::MakeLTRB(left, top, right, bottom);
86 }
87 
88 Rect PerVertexDataUVToRect(
89  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData>::iterator data,
90  ISize texture_size) {
91  Scalar right = FLT_MIN;
92  Scalar left = FLT_MAX;
93  Scalar top = FLT_MAX;
94  Scalar bottom = FLT_MIN;
95  for (int i = 0; i < 4; ++i) {
96  right = std::max(right, data[i].uv.x * texture_size.width);
97  left = std::min(left, data[i].uv.x * texture_size.width);
98  top = std::min(top, data[i].uv.y * texture_size.height);
99  bottom = std::max(bottom, data[i].uv.y * texture_size.height);
100  }
101 
102  return Rect::MakeLTRB(left, top, right, bottom);
103 }
104 
105 double GetAspectRatio(Rect rect) {
106  return static_cast<double>(rect.GetWidth()) / rect.GetHeight();
107 }
108 } // namespace
109 
110 TEST_P(TextContentsTest, SimpleComputeVertexData) {
111 #ifndef FML_OS_MACOSX
112  GTEST_SKIP() << "Results aren't stable across linux and macos.";
113 #endif
114 
115  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
116 
117  std::shared_ptr<TextFrame> text_frame =
118  MakeTextFrame("1", "ahem.ttf", TextOptions{.font_size = 50});
119 
120  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
121  std::shared_ptr<GlyphAtlasContext> atlas_context =
122  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
123  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
124  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
125  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
126  ASSERT_TRUE(context && context->IsValid());
127  std::shared_ptr<GlyphAtlas> atlas =
128  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
129  GlyphAtlas::Type::kAlphaBitmap, /*scale=*/Rational(1, 1),
130  atlas_context, text_frame, /*offset=*/{0, 0});
131 
132  ISize texture_size = atlas->GetTexture()->GetSize();
133  TextContents::ComputeVertexData(data.data(), text_frame, /*scale=*/1.0,
134  /*entity_transform=*/Matrix(),
135  /*offset=*/Vector2(0, 0),
136  /*glyph_properties=*/std::nullopt, atlas);
137 
138  Rect position_rect = PerVertexDataPositionToRect(data.begin());
139  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
140  // The -1 offset comes from Skia in `ComputeGlyphSize`. So since the font size
141  // is 50, the math appears to be to get back a 50x50 rect and apply 1 pixel
142  // of padding.
143  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
144  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
145 }
146 
147 TEST_P(TextContentsTest, SimpleComputeVertexData2x) {
148 #ifndef FML_OS_MACOSX
149  GTEST_SKIP() << "Results aren't stable across linux and macos.";
150 #endif
151 
152  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
153  std::shared_ptr<TextFrame> text_frame =
154  MakeTextFrame("1", "ahem.ttf", TextOptions{.font_size = 50});
155 
156  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
157  std::shared_ptr<GlyphAtlasContext> atlas_context =
158  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
159  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
160  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
161  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
162  ASSERT_TRUE(context && context->IsValid());
163  Rational font_scale(2, 1);
164  std::shared_ptr<GlyphAtlas> atlas =
165  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
166  GlyphAtlas::Type::kAlphaBitmap, font_scale,
167  atlas_context, text_frame, /*offset=*/{0, 0});
168 
169  ISize texture_size = atlas->GetTexture()->GetSize();
171  data.data(), text_frame, static_cast<Scalar>(font_scale),
172  /*entity_transform=*/
173  Matrix::MakeScale({static_cast<Scalar>(font_scale),
174  static_cast<Scalar>(font_scale), 1}),
175  /*offset=*/Vector2(0, 0),
176  /*glyph_properties=*/std::nullopt, atlas);
177 
178  Rect position_rect = PerVertexDataPositionToRect(data.begin());
179  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
180  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -81, 102, 102));
181  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 102, 102));
182 }
183 
184 TEST_P(TextContentsTest, MaintainsShape) {
185  std::shared_ptr<TextFrame> text_frame =
186  MakeTextFrame("th", "ahem.ttf", TextOptions{.font_size = 50});
187 
188  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
189  std::shared_ptr<GlyphAtlasContext> atlas_context =
190  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
191  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
192  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
193  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
194  ASSERT_TRUE(context && context->IsValid());
195 
196  for (int i = 0; i <= 1000; ++i) {
197  Rational font_scale(440 + i, 1000.0);
198  Rect position_rect[2];
199  Rect uv_rect[2];
200 
201  {
202  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(12);
203 
204  std::shared_ptr<GlyphAtlas> atlas =
205  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
206  GlyphAtlas::Type::kAlphaBitmap, font_scale,
207  atlas_context, text_frame, /*offset=*/{0, 0});
208  ISize texture_size = atlas->GetTexture()->GetSize();
209 
211  data.data(), text_frame, static_cast<Scalar>(font_scale),
212  /*entity_transform=*/
213  Matrix::MakeScale({static_cast<Scalar>(font_scale),
214  static_cast<Scalar>(font_scale), 1}),
215  /*offset=*/Vector2(0, 0),
216  /*glyph_properties=*/std::nullopt, atlas);
217  position_rect[0] = PerVertexDataPositionToRect(data.begin());
218  uv_rect[0] = PerVertexDataUVToRect(data.begin(), texture_size);
219  position_rect[1] = PerVertexDataPositionToRect(data.begin() + 4);
220  uv_rect[1] = PerVertexDataUVToRect(data.begin() + 4, texture_size);
221  }
222  EXPECT_NEAR(GetAspectRatio(position_rect[1]), GetAspectRatio(uv_rect[1]),
223  0.001)
224  << i;
225  }
226 }
227 
228 TEST_P(TextContentsTest, SimpleSubpixel) {
229 #ifndef FML_OS_MACOSX
230  GTEST_SKIP() << "Results aren't stable across linux and macos.";
231 #endif
232 
233  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
234 
235  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
236  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
237 
238  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
239  std::shared_ptr<GlyphAtlasContext> atlas_context =
240  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
241  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
242  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
243  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
244  ASSERT_TRUE(context && context->IsValid());
245  Point offset = Point(0.5, 0);
246  std::shared_ptr<GlyphAtlas> atlas =
247  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
249  atlas_context, text_frame, offset);
250 
251  ISize texture_size = atlas->GetTexture()->GetSize();
253  data.data(), text_frame, /*scale=*/1.0,
254  /*entity_transform=*/Matrix::MakeTranslation(offset), offset,
255  /*glyph_properties=*/std::nullopt, atlas);
256 
257  Rect position_rect = PerVertexDataPositionToRect(data.begin());
258  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
259  // The values at Point(0, 0).
260  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
261  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
262  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -41, 54, 52));
263  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 54, 52));
264 }
265 
266 TEST_P(TextContentsTest, SimpleSubpixel3x) {
267 #ifndef FML_OS_MACOSX
268  GTEST_SKIP() << "Results aren't stable across linux and macos.";
269 #endif
270 
271  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
272 
273  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
274  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
275 
276  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
277  std::shared_ptr<GlyphAtlasContext> atlas_context =
278  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
279  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
280  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
281  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
282  ASSERT_TRUE(context && context->IsValid());
283  Rational font_scale(3, 1);
284  Point offset = {0.16667, 0};
285  std::shared_ptr<GlyphAtlas> atlas =
286  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
287  GlyphAtlas::Type::kAlphaBitmap, font_scale,
288  atlas_context, text_frame, offset);
289 
290  ISize texture_size = atlas->GetTexture()->GetSize();
292  data.data(), text_frame, static_cast<Scalar>(font_scale),
293  /*entity_transform=*/
294  Matrix::MakeTranslation(offset) *
295  Matrix::MakeScale({static_cast<Scalar>(font_scale),
296  static_cast<Scalar>(font_scale), 1}),
297  offset,
298  /*glyph_properties=*/std::nullopt, atlas);
299 
300  Rect position_rect = PerVertexDataPositionToRect(data.begin());
301  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
302  // Values at Point(0, 0)
303  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -121, 152, 152));
304  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 152, 152));
305  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -121, 154, 152))
306  << "position size:" << position_rect.GetSize();
307  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 154, 152))
308  << "position size:" << position_rect.GetSize();
309 }
310 
311 TEST_P(TextContentsTest, SimpleSubpixel26) {
312 #ifndef FML_OS_MACOSX
313  GTEST_SKIP() << "Results aren't stable across linux and macos.";
314 #endif
315 
316  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
317 
318  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
319  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
320 
321  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
322  std::shared_ptr<GlyphAtlasContext> atlas_context =
323  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
324  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
325  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
326  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
327  ASSERT_TRUE(context && context->IsValid());
328  Point offset = Point(0.26, 0);
329  std::shared_ptr<GlyphAtlas> atlas =
330  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
332  atlas_context, text_frame, offset);
333 
334  ISize texture_size = atlas->GetTexture()->GetSize();
336  data.data(), text_frame, /*scale=*/1.0,
337  /*entity_transform=*/Matrix::MakeTranslation(offset), offset,
338  /*glyph_properties=*/std::nullopt, atlas);
339 
340  Rect position_rect = PerVertexDataPositionToRect(data.begin());
341  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
342  // The values without subpixel.
343  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
344  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
345  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -41, 54, 52));
346  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 54, 52));
347 }
348 
349 TEST_P(TextContentsTest, SimpleSubpixel80) {
350 #ifndef FML_OS_MACOSX
351  GTEST_SKIP() << "Results aren't stable across linux and macos.";
352 #endif
353 
354  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
355 
356  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
357  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
358 
359  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
360  std::shared_ptr<GlyphAtlasContext> atlas_context =
361  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
362  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
363  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
364  GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
365  ASSERT_TRUE(context && context->IsValid());
366  Point offset = Point(0.80, 0);
367  std::shared_ptr<GlyphAtlas> atlas =
368  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
370  atlas_context, text_frame, offset);
371 
372  ISize texture_size = atlas->GetTexture()->GetSize();
374  data.data(), text_frame, /*scale=*/1.0,
375  /*entity_transform=*/Matrix::MakeTranslation(offset), offset,
376  /*glyph_properties=*/std::nullopt, atlas);
377 
378  Rect position_rect = PerVertexDataPositionToRect(data.begin());
379  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
380  // The values without subpixel.
381  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
382  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
383  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -41, 54, 52));
384  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 54, 52));
385 }
386 
387 } // namespace testing
388 } // namespace impeller
GLenum type
Type
Describes how the glyphs are represented in the texture.
Definition: glyph_atlas.h:74
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 std::shared_ptr< TextFrame > &frame, Scalar scale, const Matrix &entity_transform, Vector2 offset, std::optional< GlyphProperties > glyph_properties, const std::shared_ptr< GlyphAtlas > &atlas)
static Rational RoundScaledFontSize(Scalar scale)
Definition: text_frame.cc:55
static std::shared_ptr< TypographerContext > Make()
#define EXPECT_RECT_NEAR(a, b)
TEST_P(AiksTest, DrawAtlasNoColor)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
static std::shared_ptr< GlyphAtlas > CreateGlyphAtlas(Context &context, const TypographerContext *typographer_context, HostBuffer &host_buffer, GlyphAtlas::Type type, Rational scale, const std::shared_ptr< GlyphAtlasContext > &atlas_context, const std::shared_ptr< TextFrame > &frame)
Point Vector2
Definition: point.h:331
float Scalar
Definition: scalar.h:19
TRect< Scalar > Rect
Definition: rect.h:792
TPoint< Scalar > Point
Definition: point.h:327
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:331
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
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:68