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(
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  ASSERT_TRUE(context && context->IsValid());
126  std::shared_ptr<GlyphAtlas> atlas =
127  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
128  GlyphAtlas::Type::kAlphaBitmap, /*scale=*/Rational(1, 1),
129  atlas_context, text_frame, /*offset=*/{0, 0});
130 
131  ISize texture_size = atlas->GetTexture()->GetSize();
132  TextContents::ComputeVertexData(data.data(), text_frame, /*scale=*/1.0,
133  /*entity_transform=*/Matrix(),
134  /*offset=*/Vector2(0, 0),
135  /*glyph_properties=*/std::nullopt, atlas);
136 
137  Rect position_rect = PerVertexDataPositionToRect(data.begin());
138  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
139  // The -1 offset comes from Skia in `ComputeGlyphSize`. So since the font size
140  // is 50, the math appears to be to get back a 50x50 rect and apply 1 pixel
141  // of padding.
142  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
143  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
144 }
145 
146 TEST_P(TextContentsTest, SimpleComputeVertexData2x) {
147 #ifndef FML_OS_MACOSX
148  GTEST_SKIP() << "Results aren't stable across linux and macos.";
149 #endif
150 
151  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
152  std::shared_ptr<TextFrame> text_frame =
153  MakeTextFrame("1", "ahem.ttf", TextOptions{.font_size = 50});
154 
155  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
156  std::shared_ptr<GlyphAtlasContext> atlas_context =
157  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
158  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
159  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter());
160  ASSERT_TRUE(context && context->IsValid());
161  Rational font_scale(2, 1);
162  std::shared_ptr<GlyphAtlas> atlas =
163  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
164  GlyphAtlas::Type::kAlphaBitmap, font_scale,
165  atlas_context, text_frame, /*offset=*/{0, 0});
166 
167  ISize texture_size = atlas->GetTexture()->GetSize();
169  data.data(), text_frame, static_cast<Scalar>(font_scale),
170  /*entity_transform=*/
171  Matrix::MakeScale({static_cast<Scalar>(font_scale),
172  static_cast<Scalar>(font_scale), 1}),
173  /*offset=*/Vector2(0, 0),
174  /*glyph_properties=*/std::nullopt, atlas);
175 
176  Rect position_rect = PerVertexDataPositionToRect(data.begin());
177  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
178  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -81, 102, 102));
179  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 102, 102));
180 }
181 
182 TEST_P(TextContentsTest, MaintainsShape) {
183  std::shared_ptr<TextFrame> text_frame =
184  MakeTextFrame("th", "ahem.ttf", TextOptions{.font_size = 50});
185 
186  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
187  std::shared_ptr<GlyphAtlasContext> atlas_context =
188  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
189  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
190  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter());
191  ASSERT_TRUE(context && context->IsValid());
192 
193  for (int i = 0; i <= 1000; ++i) {
194  Rational font_scale(440 + i, 1000.0);
195  Rect position_rect[2];
196  Rect uv_rect[2];
197 
198  {
199  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(12);
200 
201  std::shared_ptr<GlyphAtlas> atlas =
202  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
203  GlyphAtlas::Type::kAlphaBitmap, font_scale,
204  atlas_context, text_frame, /*offset=*/{0, 0});
205  ISize texture_size = atlas->GetTexture()->GetSize();
206 
208  data.data(), text_frame, static_cast<Scalar>(font_scale),
209  /*entity_transform=*/
210  Matrix::MakeScale({static_cast<Scalar>(font_scale),
211  static_cast<Scalar>(font_scale), 1}),
212  /*offset=*/Vector2(0, 0),
213  /*glyph_properties=*/std::nullopt, atlas);
214  position_rect[0] = PerVertexDataPositionToRect(data.begin());
215  uv_rect[0] = PerVertexDataUVToRect(data.begin(), texture_size);
216  position_rect[1] = PerVertexDataPositionToRect(data.begin() + 4);
217  uv_rect[1] = PerVertexDataUVToRect(data.begin() + 4, texture_size);
218  }
219  EXPECT_NEAR(GetAspectRatio(position_rect[1]), GetAspectRatio(uv_rect[1]),
220  0.001)
221  << i;
222  }
223 }
224 
225 TEST_P(TextContentsTest, SimpleSubpixel) {
226 #ifndef FML_OS_MACOSX
227  GTEST_SKIP() << "Results aren't stable across linux and macos.";
228 #endif
229 
230  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
231 
232  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
233  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
234 
235  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
236  std::shared_ptr<GlyphAtlasContext> atlas_context =
237  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
238  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
239  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter());
240  ASSERT_TRUE(context && context->IsValid());
241  Point offset = Point(0.5, 0);
242  std::shared_ptr<GlyphAtlas> atlas =
243  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
245  atlas_context, text_frame, offset);
246 
247  ISize texture_size = atlas->GetTexture()->GetSize();
249  data.data(), text_frame, /*scale=*/1.0,
250  /*entity_transform=*/Matrix::MakeTranslation(offset), offset,
251  /*glyph_properties=*/std::nullopt, atlas);
252 
253  Rect position_rect = PerVertexDataPositionToRect(data.begin());
254  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
255  // The values at Point(0, 0).
256  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
257  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
258  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -41, 54, 52));
259  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 54, 52));
260 }
261 
262 TEST_P(TextContentsTest, SimpleSubpixel3x) {
263 #ifndef FML_OS_MACOSX
264  GTEST_SKIP() << "Results aren't stable across linux and macos.";
265 #endif
266 
267  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
268 
269  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
270  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
271 
272  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
273  std::shared_ptr<GlyphAtlasContext> atlas_context =
274  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
275  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
276  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter());
277  ASSERT_TRUE(context && context->IsValid());
278  Rational font_scale(3, 1);
279  Point offset = {0.16667, 0};
280  std::shared_ptr<GlyphAtlas> atlas =
281  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
282  GlyphAtlas::Type::kAlphaBitmap, font_scale,
283  atlas_context, text_frame, offset);
284 
285  ISize texture_size = atlas->GetTexture()->GetSize();
287  data.data(), text_frame, static_cast<Scalar>(font_scale),
288  /*entity_transform=*/
290  Matrix::MakeScale({static_cast<Scalar>(font_scale),
291  static_cast<Scalar>(font_scale), 1}),
292  offset,
293  /*glyph_properties=*/std::nullopt, atlas);
294 
295  Rect position_rect = PerVertexDataPositionToRect(data.begin());
296  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
297  // Values at Point(0, 0)
298  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -121, 152, 152));
299  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 152, 152));
300  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -121, 154, 152))
301  << "position size:" << position_rect.GetSize();
302  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 154, 152))
303  << "position size:" << position_rect.GetSize();
304 }
305 
306 TEST_P(TextContentsTest, SimpleSubpixel26) {
307 #ifndef FML_OS_MACOSX
308  GTEST_SKIP() << "Results aren't stable across linux and macos.";
309 #endif
310 
311  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
312 
313  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
314  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
315 
316  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
317  std::shared_ptr<GlyphAtlasContext> atlas_context =
318  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
319  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
320  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter());
321  ASSERT_TRUE(context && context->IsValid());
322  Point offset = Point(0.26, 0);
323  std::shared_ptr<GlyphAtlas> atlas =
324  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
326  atlas_context, text_frame, offset);
327 
328  ISize texture_size = atlas->GetTexture()->GetSize();
330  data.data(), text_frame, /*scale=*/1.0,
331  /*entity_transform=*/Matrix::MakeTranslation(offset), offset,
332  /*glyph_properties=*/std::nullopt, atlas);
333 
334  Rect position_rect = PerVertexDataPositionToRect(data.begin());
335  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
336  // The values without subpixel.
337  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
338  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
339  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -41, 54, 52));
340  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 54, 52));
341 }
342 
343 TEST_P(TextContentsTest, SimpleSubpixel80) {
344 #ifndef FML_OS_MACOSX
345  GTEST_SKIP() << "Results aren't stable across linux and macos.";
346 #endif
347 
348  std::vector<GlyphAtlasPipeline::VertexShader::PerVertexData> data(4);
349 
350  std::shared_ptr<TextFrame> text_frame = MakeTextFrame(
351  "1", "ahem.ttf", TextOptions{.font_size = 50, .is_subpixel = true});
352 
353  std::shared_ptr<TypographerContext> context = TypographerContextSkia::Make();
354  std::shared_ptr<GlyphAtlasContext> atlas_context =
355  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
356  std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
357  GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter());
358  ASSERT_TRUE(context && context->IsValid());
359  Point offset = Point(0.80, 0);
360  std::shared_ptr<GlyphAtlas> atlas =
361  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
363  atlas_context, text_frame, offset);
364 
365  ISize texture_size = atlas->GetTexture()->GetSize();
367  data.data(), text_frame, /*scale=*/1.0,
368  /*entity_transform=*/Matrix::MakeTranslation(offset), offset,
369  /*glyph_properties=*/std::nullopt, atlas);
370 
371  Rect position_rect = PerVertexDataPositionToRect(data.begin());
372  Rect uv_rect = PerVertexDataUVToRect(data.begin(), texture_size);
373  // The values without subpixel.
374  // EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-1, -41, 52, 52));
375  // EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 52, 52));
376  EXPECT_RECT_NEAR(position_rect, Rect::MakeXYWH(-2, -41, 54, 52));
377  EXPECT_RECT_NEAR(uv_rect, Rect::MakeXYWH(1.0, 1.0, 54, 52));
378 }
379 
380 } // namespace testing
381 } // 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)
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:18
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
const Scalar scale
SeparatedVector2 offset
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:67