| 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
5 #include "flutter/impeller/geometry/geometry_asserts.h"
6 #include "flutter/impeller/renderer/testing/mocks.h"
7 #include "flutter/testing/testing.h"
8 #include "impeller/entity/contents/text_contents.h"
9 #include "impeller/playground/playground_test.h"
10 #include "impeller/typographer/backends/skia/text_frame_skia.h"
11 #include "impeller/typographer/backends/skia/typographer_context_skia.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
20 using TextContentsTest = PlaygroundTest;
21 INSTANTIATE_PLAYGROUND_SUITE(TextContentsTest);
22
23 using ::testing::Return;
24
25 namespace {
26 struct TextOptions {
27 Scalar font_size = 50;
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,
56 GlyphAtlas::Type type,
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=*/
64 Matrix::MakeScale(
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();
168 TextContents::ComputeVertexData(
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
207 TextContents::ComputeVertexData(
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,
244 GlyphAtlas::Type::kAlphaBitmap, /*scale=*/Rational(1),
245 atlas_context, text_frame, offset);
246
247 ISize texture_size = atlas->GetTexture()->GetSize();
248 TextContents::ComputeVertexData(
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();
286 TextContents::ComputeVertexData(
287 data.data(), text_frame, static_cast<Scalar>(font_scale),
288 /*entity_transform=*/
289 Matrix::MakeTranslation(offset) *
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,
325 GlyphAtlas::Type::kAlphaBitmap, /*scale=*/Rational(1),
326 atlas_context, text_frame, offset);
327
328 ISize texture_size = atlas->GetTexture()->GetSize();
329 TextContents::ComputeVertexData(
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,
362 GlyphAtlas::Type::kAlphaBitmap, /*scale=*/Rational(1),
363 atlas_context, text_frame, offset);
364
365 ISize texture_size = atlas->GetTexture()->GetSize();
366 TextContents::ComputeVertexData(
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
type
GLenum type
Definition: blit_command_gles.cc:151
impeller::GlyphAtlas::Type
Type
Describes how the glyphs are represented in the texture.
Definition: glyph_atlas.h:74
impeller::GlyphAtlas::Type::kAlphaBitmap
@ kAlphaBitmap
impeller::HostBuffer::Create
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
impeller::PlaygroundTest
Definition: playground_test.h:22
impeller::Rational
Definition: rational.h:13
impeller::TextContents::ComputeVertexData
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)
Definition: text_contents.cc:96
impeller::TextFrame::RoundScaledFontSize
static Rational RoundScaledFontSize(Scalar scale)
Definition: text_frame.cc:55
impeller::TypographerContextSkia::Make
static std::shared_ptr< TypographerContext > Make()
Definition: typographer_context_skia.cc:77
geometry_asserts.h
EXPECT_RECT_NEAR
#define EXPECT_RECT_NEAR(a, b)
Definition: geometry_asserts.h:203
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:60
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller::testing::CreateGlyphAtlas
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)
Definition: typographer_unittests.cc:32
impeller
Definition: allocation.cc:12
impeller::Vector2
Point Vector2
Definition: point.h:331
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::Rect
TRect< Scalar > Rect
Definition: rect.h:792
impeller::Point
TPoint< Scalar > Point
Definition: point.h:327
impeller::MakeTextFrameFromTextBlobSkia
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
Definition: text_frame_skia.cc:45
impeller::ISize
ISize64 ISize
Definition: size.h:162
playground_test.h
scale
const Scalar scale
Definition: stroke_path_geometry.cc:319
offset
SeparatedVector2 offset
Definition: stroke_path_geometry.cc:322
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
impeller::Matrix::MakeScale
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
impeller::TPoint< Scalar >
impeller::TRect< Scalar >
impeller::TRect::GetSize
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
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
impeller::TSize
Definition: size.h:25
text_contents.h
font_size
Scalar font_size
Definition: text_contents_unittests.cc:27
is_subpixel
bool is_subpixel
Definition: text_contents_unittests.cc:28
text_frame_skia.h
data
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:67
typographer_context_skia.h