| Flutter Impeller |

typographer_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/testing/dl_test_snippets.h"
6 #include "flutter/testing/testing.h"
7 #include "gtest/gtest.h"
8 #include "impeller/core/host_buffer.h"
9 #include "impeller/playground/playground.h"
10 #include "impeller/playground/playground_test.h"
11 #include "impeller/typographer/backends/skia/text_frame_skia.h"
12 #include "impeller/typographer/backends/skia/typographer_context_skia.h"
13 #include "impeller/typographer/font_glyph_pair.h"
14 #include "impeller/typographer/lazy_glyph_atlas.h"
15 #include "impeller/typographer/rectangle_packer.h"
16 #include "third_party/skia/include/core/SkFont.h"
17 #include "third_party/skia/include/core/SkFontMgr.h"
18 #include "third_party/skia/include/core/SkRect.h"
19 #include "third_party/skia/include/core/SkTextBlob.h"
20 #include "third_party/skia/include/core/SkTypeface.h"
21 #include "txt/platform.h"
22
23 // TODO(zanderso): https://github.com/flutter/flutter/issues/127701
24 // NOLINTBEGIN(bugprone-unchecked-optional-access)
25
26 namespace impeller {
27 namespace testing {
28
29 using TypographerTest = PlaygroundTest;
30 INSTANTIATE_PLAYGROUND_SUITE(TypographerTest);
31
32 static std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
33 Context& context,
34 const TypographerContext* typographer_context,
35 HostBuffer& data_host_buffer,
36 GlyphAtlas::Type type,
37 Rational scale,
38 const std::shared_ptr<GlyphAtlasContext>& atlas_context,
39 const std::shared_ptr<TextFrame>& frame) {
40 frame->SetPerFrameData(scale, {0, 0}, Matrix(), std::nullopt);
41 return typographer_context->CreateGlyphAtlas(context, type, data_host_buffer,
42 atlas_context, {frame});
43 }
44
45 static std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
46 Context& context,
47 const TypographerContext* typographer_context,
48 HostBuffer& data_host_buffer,
49 GlyphAtlas::Type type,
50 Rational scale,
51 const std::shared_ptr<GlyphAtlasContext>& atlas_context,
52 const std::vector<std::shared_ptr<TextFrame>>& frames,
53 const std::vector<std::optional<GlyphProperties>>& properties) {
54 size_t offset = 0;
55 for (auto& frame : frames) {
56 frame->SetPerFrameData(scale, {0, 0}, Matrix(), properties[offset++]);
57 }
58 return typographer_context->CreateGlyphAtlas(context, type, data_host_buffer,
59 atlas_context, frames);
60 }
61
62 TEST_P(TypographerTest, CanConvertTextBlob) {
63 SkFont font = flutter::testing::CreateTestFontOfSize(12);
64 auto blob = SkTextBlob::MakeFromString(
65 "the quick brown fox jumped over the lazy dog.", font);
66 ASSERT_TRUE(blob);
67 auto frame = MakeTextFrameFromTextBlobSkia(blob);
68 ASSERT_EQ(frame->GetRunCount(), 1u);
69 for (const auto& run : frame->GetRuns()) {
70 ASSERT_TRUE(run.IsValid());
71 ASSERT_EQ(run.GetGlyphCount(), 45u);
72 }
73 }
74
75 TEST_P(TypographerTest, CanCreateRenderContext) {
76 auto context = TypographerContextSkia::Make();
77 ASSERT_TRUE(context && context->IsValid());
78 }
79
80 TEST_P(TypographerTest, CanCreateGlyphAtlas) {
81 auto context = TypographerContextSkia::Make();
82 auto atlas_context =
83 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
84 auto data_host_buffer = HostBuffer::Create(
85 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
86 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
87 ASSERT_TRUE(context && context->IsValid());
88 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
89 auto blob = SkTextBlob::MakeFromString("hello", sk_font);
90 ASSERT_TRUE(blob);
91 auto atlas =
92 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
93 GlyphAtlas::Type::kAlphaBitmap, Rational(1),
94 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
95
96 ASSERT_NE(atlas, nullptr);
97 ASSERT_NE(atlas->GetTexture(), nullptr);
98 ASSERT_EQ(atlas->GetType(), GlyphAtlas::Type::kAlphaBitmap);
99 ASSERT_EQ(atlas->GetGlyphCount(), 4llu);
100
101 std::optional<impeller::ScaledFont> first_scaled_font;
102 std::optional<impeller::SubpixelGlyph> first_glyph;
103 Rect first_rect;
104 atlas->IterateGlyphs([&](const ScaledFont& scaled_font,
105 const SubpixelGlyph& glyph,
106 const Rect& rect) -> bool {
107 first_scaled_font = scaled_font;
108 first_glyph = glyph;
109 first_rect = rect;
110 return false;
111 });
112
113 ASSERT_TRUE(first_scaled_font.has_value());
114 ASSERT_TRUE(atlas
115 ->FindFontGlyphBounds(
116 {first_scaled_font.value(), first_glyph.value()})
117 .has_value());
118 }
119
120 TEST_P(TypographerTest, LazyAtlasTracksColor) {
121 auto data_host_buffer = HostBuffer::Create(
122 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
123 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
124 #if FML_OS_MACOSX
125 auto mapping = flutter::testing::OpenFixtureAsSkData("Apple Color Emoji.ttc");
126 #else
127 auto mapping = flutter::testing::OpenFixtureAsSkData("NotoColorEmoji.ttf");
128 #endif
129 ASSERT_TRUE(mapping);
130 sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
131 SkFont emoji_font(font_mgr->makeFromData(mapping), 50.0);
132 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
133
134 auto blob = SkTextBlob::MakeFromString("hello", sk_font);
135 ASSERT_TRUE(blob);
136 auto frame = MakeTextFrameFromTextBlobSkia(blob);
137
138 ASSERT_FALSE(frame->GetAtlasType() == GlyphAtlas::Type::kColorBitmap);
139
140 LazyGlyphAtlas lazy_atlas(TypographerContextSkia::Make());
141
142 lazy_atlas.AddTextFrame(frame, Rational(1), {0, 0}, Matrix(), {});
143
144 frame = MakeTextFrameFromTextBlobSkia(
145 SkTextBlob::MakeFromString("😀 ", emoji_font));
146
147 ASSERT_TRUE(frame->GetAtlasType() == GlyphAtlas::Type::kColorBitmap);
148
149 lazy_atlas.AddTextFrame(frame, Rational(1), {0, 0}, Matrix(), {});
150
151 // Creates different atlases for color and red bitmap.
152 auto color_atlas = lazy_atlas.CreateOrGetGlyphAtlas(
153 *GetContext(), *data_host_buffer, GlyphAtlas::Type::kColorBitmap);
154
155 auto bitmap_atlas = lazy_atlas.CreateOrGetGlyphAtlas(
156 *GetContext(), *data_host_buffer, GlyphAtlas::Type::kAlphaBitmap);
157
158 ASSERT_FALSE(color_atlas == bitmap_atlas);
159 }
160
161 TEST_P(TypographerTest, GlyphAtlasWithOddUniqueGlyphSize) {
162 auto context = TypographerContextSkia::Make();
163 auto atlas_context =
164 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
165 auto data_host_buffer = HostBuffer::Create(
166 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
167 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
168 ASSERT_TRUE(context && context->IsValid());
169 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
170 auto blob = SkTextBlob::MakeFromString("AGH", sk_font);
171 ASSERT_TRUE(blob);
172 auto atlas =
173 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
174 GlyphAtlas::Type::kAlphaBitmap, Rational(1),
175 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
176 ASSERT_NE(atlas, nullptr);
177 ASSERT_NE(atlas->GetTexture(), nullptr);
178
179 EXPECT_EQ(atlas->GetTexture()->GetSize().width, 4096u);
180 EXPECT_EQ(atlas->GetTexture()->GetSize().height, 1024u);
181 }
182
183 TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) {
184 auto context = TypographerContextSkia::Make();
185 auto atlas_context =
186 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
187 auto data_host_buffer = HostBuffer::Create(
188 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
189 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
190 ASSERT_TRUE(context && context->IsValid());
191 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
192 auto blob = SkTextBlob::MakeFromString("spooky skellingtons", sk_font);
193 ASSERT_TRUE(blob);
194 auto atlas =
195 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
196 GlyphAtlas::Type::kAlphaBitmap, Rational(1),
197 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
198 ASSERT_NE(atlas, nullptr);
199 ASSERT_NE(atlas->GetTexture(), nullptr);
200 ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas());
201
202 // now attempt to re-create an atlas with the same text blob.
203
204 auto next_atlas =
205 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
206 GlyphAtlas::Type::kAlphaBitmap, Rational(1),
207 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
208 ASSERT_EQ(atlas, next_atlas);
209 ASSERT_EQ(atlas_context->GetGlyphAtlas(), atlas);
210 }
211
212 TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) {
213 auto data_host_buffer = HostBuffer::Create(
214 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
215 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
216 auto context = TypographerContextSkia::Make();
217 auto atlas_context =
218 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
219 ASSERT_TRUE(context && context->IsValid());
220
221 const char* test_string =
222 "QWERTYUIOPASDFGHJKLZXCVBNMqewrtyuiopasdfghjklzxcvbnm,.<>[]{};':"
223 "2134567890-=!@#$%^&*()_+"
224 "œ∑´®†¥¨ˆøπ““‘‘åß∂ƒ©˙∆˚¬…æ≈ç√∫˜µ≤≥≥≥≥÷¡™£¢∞§¶•ªº–≠⁄€‹›ﬁﬂ‡°·‚—±Œ„´‰Á¨Ø∏”’/"
225 "* Í˝ */¸˛Ç◊ı˜Â¯˘¿";
226
227 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
228 auto blob = SkTextBlob::MakeFromString(test_string, sk_font);
229 ASSERT_TRUE(blob);
230
231 size_t size_count = 8;
232 std::vector<std::shared_ptr<TextFrame>> frames;
233 for (size_t index = 0; index < size_count; index += 1) {
234 frames.push_back(MakeTextFrameFromTextBlobSkia(blob));
235 frames.back()->SetPerFrameData(Rational(6 * index, 10), {0, 0}, Matrix(),
236 {});
237 };
238 auto atlas =
239 context->CreateGlyphAtlas(*GetContext(), GlyphAtlas::Type::kAlphaBitmap,
240 *data_host_buffer, atlas_context, frames);
241 ASSERT_NE(atlas, nullptr);
242 ASSERT_NE(atlas->GetTexture(), nullptr);
243
244 std::set<uint16_t> unique_glyphs;
245 std::vector<uint16_t> total_glyphs;
246 atlas->IterateGlyphs([&](const ScaledFont& scaled_font,
247 const SubpixelGlyph& glyph, const Rect& rect) {
248 unique_glyphs.insert(glyph.glyph.index);
249 total_glyphs.push_back(glyph.glyph.index);
250 return true;
251 });
252
253 // These numbers may be different due to subpixel positions.
254 EXPECT_LE(unique_glyphs.size() * size_count, atlas->GetGlyphCount());
255 EXPECT_EQ(total_glyphs.size(), atlas->GetGlyphCount());
256
257 EXPECT_TRUE(atlas->GetGlyphCount() > 0);
258 EXPECT_TRUE(atlas->GetTexture()->GetSize().width > 0);
259 EXPECT_TRUE(atlas->GetTexture()->GetSize().height > 0);
260 }
261
262 TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) {
263 auto data_host_buffer = HostBuffer::Create(
264 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
265 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
266 auto context = TypographerContextSkia::Make();
267 auto atlas_context =
268 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
269 ASSERT_TRUE(context && context->IsValid());
270 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
271 auto blob = SkTextBlob::MakeFromString("spooky 1", sk_font);
272 ASSERT_TRUE(blob);
273 auto atlas =
274 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
275 GlyphAtlas::Type::kAlphaBitmap, Rational(1),
276 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
277 auto old_packer = atlas_context->GetRectPacker();
278
279 ASSERT_NE(atlas, nullptr);
280 ASSERT_NE(atlas->GetTexture(), nullptr);
281 ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas());
282
283 auto* first_texture = atlas->GetTexture().get();
284
285 // Now create a new glyph atlas with a nearly identical blob.
286
287 auto blob2 = SkTextBlob::MakeFromString("spooky 2", sk_font);
288 auto next_atlas =
289 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
290 GlyphAtlas::Type::kAlphaBitmap, Rational(1),
291 atlas_context, MakeTextFrameFromTextBlobSkia(blob2));
292 ASSERT_EQ(atlas, next_atlas);
293 auto* second_texture = next_atlas->GetTexture().get();
294
295 auto new_packer = atlas_context->GetRectPacker();
296
297 ASSERT_EQ(second_texture, first_texture);
298 ASSERT_EQ(old_packer, new_packer);
299 }
300
301 TEST_P(TypographerTest, GlyphColorIsPartOfCacheKey) {
302 auto data_host_buffer = HostBuffer::Create(
303 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
304 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
305 #if FML_OS_MACOSX
306 auto mapping = flutter::testing::OpenFixtureAsSkData("Apple Color Emoji.ttc");
307 #else
308 auto mapping = flutter::testing::OpenFixtureAsSkData("NotoColorEmoji.ttf");
309 #endif
310 ASSERT_TRUE(mapping);
311 sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
312 SkFont emoji_font(font_mgr->makeFromData(mapping), 50.0);
313
314 auto context = TypographerContextSkia::Make();
315 auto atlas_context =
316 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kColorBitmap);
317
318 // Create two frames with the same character and a different color, expect
319 // that it adds a character.
320 auto frame = MakeTextFrameFromTextBlobSkia(
321 SkTextBlob::MakeFromString("😂", emoji_font));
322 auto frame_2 = MakeTextFrameFromTextBlobSkia(
323 SkTextBlob::MakeFromString("😂", emoji_font));
324 std::vector<std::optional<GlyphProperties>> properties = {
325 GlyphProperties{.color = Color::Red()},
326 GlyphProperties{.color = Color::Blue()},
327 };
328
329 auto next_atlas =
330 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
331 GlyphAtlas::Type::kColorBitmap, Rational(1),
332 atlas_context, {frame, frame_2}, properties);
333
334 EXPECT_EQ(next_atlas->GetGlyphCount(), 2u);
335 }
336
337 TEST_P(TypographerTest, GlyphColorIsIgnoredForNonEmojiFonts) {
338 auto data_host_buffer = HostBuffer::Create(
339 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
340 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
341 sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
342 sk_sp<SkTypeface> typeface =
343 font_mgr->matchFamilyStyle("Arial", SkFontStyle::Normal());
344 SkFont sk_font(typeface, 0.5f);
345
346 auto context = TypographerContextSkia::Make();
347 auto atlas_context =
348 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kColorBitmap);
349
350 // Create two frames with the same character and a different color, but as a
351 // non-emoji font the text frame constructor will ignore it.
352 auto frame =
353 MakeTextFrameFromTextBlobSkia(SkTextBlob::MakeFromString("A", sk_font));
354 auto frame_2 =
355 MakeTextFrameFromTextBlobSkia(SkTextBlob::MakeFromString("A", sk_font));
356 std::vector<std::optional<GlyphProperties>> properties = {
357 GlyphProperties{},
358 GlyphProperties{},
359 };
360
361 auto next_atlas =
362 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
363 GlyphAtlas::Type::kColorBitmap, Rational(1),
364 atlas_context, {frame, frame_2}, properties);
365
366 EXPECT_EQ(next_atlas->GetGlyphCount(), 1u);
367 }
368
369 TEST_P(TypographerTest, RectanglePackerAddsNonoverlapingRectangles) {
370 auto packer = RectanglePacker::Factory(200, 100);
371 ASSERT_NE(packer, nullptr);
372 ASSERT_EQ(packer->PercentFull(), 0);
373
374 const SkIRect packer_area = SkIRect::MakeXYWH(0, 0, 200, 100);
375
376 IPoint16 first_output = {-1, -1}; // Fill with sentinel values
377 ASSERT_TRUE(packer->AddRect(20, 20, &first_output));
378 // Make sure the rectangle is placed such that it is inside the bounds of
379 // the packer's area.
380 const SkIRect first_rect =
381 SkIRect::MakeXYWH(first_output.x(), first_output.y(), 20, 20);
382 ASSERT_TRUE(SkIRect::Intersects(packer_area, first_rect));
383
384 // Initial area was 200 x 100 = 20_000
385 // We added 20x20 = 400. 400 / 20_000 == 0.02 == 2%
386 ASSERT_TRUE(flutter::testing::NumberNear(packer->PercentFull(), 0.02));
387
388 IPoint16 second_output = {-1, -1};
389 ASSERT_TRUE(packer->AddRect(140, 90, &second_output));
390 const SkIRect second_rect =
391 SkIRect::MakeXYWH(second_output.x(), second_output.y(), 140, 90);
392 // Make sure the rectangle is placed such that it is inside the bounds of
393 // the packer's area but not in the are of the first rectangle.
394 ASSERT_TRUE(SkIRect::Intersects(packer_area, second_rect));
395 ASSERT_FALSE(SkIRect::Intersects(first_rect, second_rect));
396
397 // We added another 90 x 140 = 12_600 units, now taking us to 13_000
398 // 13_000 / 20_000 == 0.65 == 65%
399 ASSERT_TRUE(flutter::testing::NumberNear(packer->PercentFull(), 0.65));
400
401 // There's enough area to add this rectangle, but no space big enough for
402 // the 50 units of width.
403 IPoint16 output;
404 ASSERT_FALSE(packer->AddRect(50, 50, &output));
405 // Should be unchanged.
406 ASSERT_TRUE(flutter::testing::NumberNear(packer->PercentFull(), 0.65));
407
408 packer->Reset();
409 // Should be empty now.
410 ASSERT_EQ(packer->PercentFull(), 0);
411 }
412
413 TEST(TypographerTest, RectanglePackerFillsRows) {
414 auto skyline = RectanglePacker::Factory(257, 256);
415
416 // Fill up the first row.
417 IPoint16 loc;
418 for (auto i = 0u; i < 16; i++) {
419 skyline->AddRect(16, 16, &loc);
420 }
421 // Last rectangle still in first row.
422 EXPECT_EQ(loc.x(), 256 - 16);
423 EXPECT_EQ(loc.y(), 0);
424
425 // Fill up second row.
426 for (auto i = 0u; i < 16; i++) {
427 skyline->AddRect(16, 16, &loc);
428 }
429
430 EXPECT_EQ(loc.x(), 256 - 16);
431 EXPECT_EQ(loc.y(), 16);
432 }
433
434 TEST_P(TypographerTest, GlyphAtlasTextureWillGrowTilMaxTextureSize) {
435 if (GetBackend() == PlaygroundBackend::kOpenGLES) {
436 GTEST_SKIP() << "Atlas growth isn't supported for OpenGLES currently.";
437 }
438
439 auto data_host_buffer = HostBuffer::Create(
440 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
441 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
442 auto context = TypographerContextSkia::Make();
443 auto atlas_context =
444 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
445 ASSERT_TRUE(context && context->IsValid());
446 SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
447 auto blob = SkTextBlob::MakeFromString("A", sk_font);
448 ASSERT_TRUE(blob);
449 auto atlas =
450 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
451 GlyphAtlas::Type::kAlphaBitmap, Rational(1),
452 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
453 // Continually append new glyphs until the glyph size grows to the maximum.
454 // Note that the sizes here are more or less experimentally determined, but
455 // the important expectation is that the atlas size will shrink again after
456 // growing to the maximum size.
457 constexpr ISize expected_sizes[13] = {
458 {4096, 4096}, //
459 {4096, 4096}, //
460 {4096, 8192}, //
461 {4096, 8192}, //
462 {4096, 8192}, //
463 {4096, 8192}, //
464 {4096, 16384}, //
465 {4096, 16384}, //
466 {4096, 16384}, //
467 {4096, 16384}, //
468 {4096, 16384}, //
469 {4096, 16384}, //
470 {4096, 4096} // Shrinks!
471 };
472
473 SkFont sk_font_small = flutter::testing::CreateTestFontOfSize(10);
474
475 for (int i = 0; i < 13; i++) {
476 SkTextBlobBuilder builder;
477
478 auto add_char = [&](const SkFont& sk_font, char c) {
479 int count = sk_font.countText(&c, 1, SkTextEncoding::kUTF8);
480 auto buffer = builder.allocRunPos(sk_font, count);
481 sk_font.textToGlyphs(&c, 1, SkTextEncoding::kUTF8,
482 {buffer.glyphs, count});
483 sk_font.getPos({buffer.glyphs, count}, {buffer.points(), count},
484 {0, 0} /*=origin*/);
485 };
486
487 SkFont sk_font = flutter::testing::CreateTestFontOfSize(50 + i);
488 add_char(sk_font, 'A');
489 add_char(sk_font_small, 'B');
490 auto blob = builder.make();
491
492 atlas =
493 CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
494 GlyphAtlas::Type::kAlphaBitmap, Rational(50 + i, 1),
495 atlas_context, MakeTextFrameFromTextBlobSkia(blob));
496 ASSERT_TRUE(!!atlas);
497 EXPECT_EQ(atlas->GetTexture()->GetTextureDescriptor().size,
498 expected_sizes[i]);
499 }
500
501 // The final atlas should contain both the "A" glyph (which was not present
502 // in the previous atlas) and the "B" glyph (which existed in the previous
503 // atlas).
504 ASSERT_EQ(atlas->GetGlyphCount(), 2u);
505 }
506
507 TEST_P(TypographerTest, TextFrameInitialBoundsArePlaceholder) {
508 SkFont font = flutter::testing::CreateTestFontOfSize(12);
509 auto blob = SkTextBlob::MakeFromString(
510 "the quick brown fox jumped over the lazy dog.", font);
511 ASSERT_TRUE(blob);
512 auto frame = MakeTextFrameFromTextBlobSkia(blob);
513
514 EXPECT_FALSE(frame->IsFrameComplete());
515
516 auto context = TypographerContextSkia::Make();
517 auto atlas_context =
518 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
519 auto data_host_buffer = HostBuffer::Create(
520 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
521 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
522
523 auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
524 GlyphAtlas::Type::kAlphaBitmap,
525 /*scale=*/Rational(1), atlas_context, frame);
526
527 // The glyph position in the atlas was not known when this value
528 // was recorded. It is marked as a placeholder.
529 EXPECT_TRUE(frame->IsFrameComplete());
530 EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
531
532 atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
533 GlyphAtlas::Type::kAlphaBitmap,
534 /*scale=*/Rational(1), atlas_context, frame);
535
536 // The second time the glyph is rendered, the bounds are correcly known.
537 EXPECT_TRUE(frame->IsFrameComplete());
538 EXPECT_FALSE(frame->GetFrameBounds(0).is_placeholder);
539 }
540
541 TEST_P(TypographerTest, TextFrameInvalidationWithScale) {
542 SkFont font = flutter::testing::CreateTestFontOfSize(12);
543 auto blob = SkTextBlob::MakeFromString(
544 "the quick brown fox jumped over the lazy dog.", font);
545 ASSERT_TRUE(blob);
546 auto frame = MakeTextFrameFromTextBlobSkia(blob);
547
548 EXPECT_FALSE(frame->IsFrameComplete());
549
550 auto context = TypographerContextSkia::Make();
551 auto atlas_context =
552 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
553 auto data_host_buffer = HostBuffer::Create(
554 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
555 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
556
557 auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
558 GlyphAtlas::Type::kAlphaBitmap,
559 /*scale=*/Rational(1), atlas_context, frame);
560
561 // The glyph position in the atlas was not known when this value
562 // was recorded. It is marked as a placeholder.
563 EXPECT_TRUE(frame->IsFrameComplete());
564 EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
565
566 // Change the scale and the glyph data will still be a placeholder, as the
567 // old data is no longer valid.
568 atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
569 GlyphAtlas::Type::kAlphaBitmap,
570 /*scale=*/Rational(2), atlas_context, frame);
571
572 // The second time the glyph is rendered, the bounds are correcly known.
573 EXPECT_TRUE(frame->IsFrameComplete());
574 EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
575 }
576
577 TEST_P(TypographerTest, TextFrameAtlasGenerationTracksState) {
578 SkFont font = flutter::testing::CreateTestFontOfSize(12);
579 auto blob = SkTextBlob::MakeFromString(
580 "the quick brown fox jumped over the lazy dog.", font);
581 ASSERT_TRUE(blob);
582 auto frame = MakeTextFrameFromTextBlobSkia(blob);
583
584 EXPECT_FALSE(frame->IsFrameComplete());
585
586 auto context = TypographerContextSkia::Make();
587 auto atlas_context =
588 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
589 auto data_host_buffer = HostBuffer::Create(
590 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
591 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
592
593 auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
594 GlyphAtlas::Type::kAlphaBitmap,
595 /*scale=*/Rational(1), atlas_context, frame);
596
597 // The glyph position in the atlas was not known when this value
598 // was recorded. It is marked as a placeholder.
599 EXPECT_TRUE(frame->IsFrameComplete());
600 EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
601 if (GetBackend() == PlaygroundBackend::kOpenGLES) {
602 // OpenGLES must always increase the atlas backend if the texture grows.
603 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
604 } else {
605 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
606 }
607
608 atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
609 GlyphAtlas::Type::kAlphaBitmap,
610 /*scale=*/Rational(1), atlas_context, frame);
611
612 // The second time the glyph is rendered, the bounds are correcly known.
613 EXPECT_TRUE(frame->IsFrameComplete());
614 EXPECT_FALSE(frame->GetFrameBounds(0).is_placeholder);
615 if (GetBackend() == PlaygroundBackend::kOpenGLES) {
616 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
617 } else {
618 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
619 }
620
621 // Force increase the generation.
622 atlas_context->GetGlyphAtlas()->SetAtlasGeneration(2u);
623 atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
624 GlyphAtlas::Type::kAlphaBitmap,
625 /*scale=*/Rational(1), atlas_context, frame);
626
627 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 2u);
628 }
629
630 TEST_P(TypographerTest, InvalidAtlasForcesRepopulation) {
631 SkFont font = flutter::testing::CreateTestFontOfSize(12);
632 auto blob = SkTextBlob::MakeFromString(
633 "the quick brown fox jumped over the lazy dog.", font);
634 ASSERT_TRUE(blob);
635 auto frame = MakeTextFrameFromTextBlobSkia(blob);
636
637 EXPECT_FALSE(frame->IsFrameComplete());
638
639 auto context = TypographerContextSkia::Make();
640 auto atlas_context =
641 context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
642 auto data_host_buffer = HostBuffer::Create(
643 GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter(),
644 GetContext()->GetCapabilities()->GetMinimumUniformAlignment());
645
646 auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
647 GlyphAtlas::Type::kAlphaBitmap,
648 /*scale=*/Rational(1), atlas_context, frame);
649
650 // The glyph position in the atlas was not known when this value
651 // was recorded. It is marked as a placeholder.
652 EXPECT_TRUE(frame->IsFrameComplete());
653 EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
654 if (GetBackend() == PlaygroundBackend::kOpenGLES) {
655 // OpenGLES must always increase the atlas backend if the texture grows.
656 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
657 } else {
658 EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
659 }
660
661 auto second_context = TypographerContextSkia::Make();
662 auto second_atlas_context =
663 second_context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
664
665 EXPECT_FALSE(second_atlas_context->GetGlyphAtlas()->IsValid());
666
667 atlas = CreateGlyphAtlas(*GetContext(), second_context.get(),
668 *data_host_buffer, GlyphAtlas::Type::kAlphaBitmap,
669 /*scale=*/Rational(1), second_atlas_context, frame);
670
671 EXPECT_TRUE(second_atlas_context->GetGlyphAtlas()->IsValid());
672 }
673
674 } // namespace testing
675 } // namespace impeller
676
677 // NOLINTEND(bugprone-unchecked-optional-access)
type
GLenum type
Definition: blit_command_gles.cc:153
impeller::Context
To do anything rendering related with Impeller, you need a context.
Definition: context.h:65
impeller::GlyphAtlas::Type
Type
Describes how the glyphs are represented in the texture.
Definition: glyph_atlas.h:41
impeller::GlyphAtlas::Type::kAlphaBitmap
@ kAlphaBitmap
impeller::GlyphAtlas::Type::kColorBitmap
@ kColorBitmap
impeller::HostBuffer
Definition: host_buffer.h:26
impeller::HostBuffer::Create
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
impeller::LazyGlyphAtlas
Definition: lazy_glyph_atlas.h:16
impeller::LazyGlyphAtlas::CreateOrGetGlyphAtlas
const std::shared_ptr< GlyphAtlas > & CreateOrGetGlyphAtlas(Context &context, HostBuffer &host_buffer, GlyphAtlas::Type type) const
Definition: lazy_glyph_atlas.cc:55
impeller::LazyGlyphAtlas::AddTextFrame
void AddTextFrame(const std::shared_ptr< TextFrame > &frame, Rational scale, Point offset, const Matrix &transform, std::optional< GlyphProperties > properties)
Definition: lazy_glyph_atlas.cc:34
impeller::PlaygroundTest
Definition: playground_test.h:23
impeller::Rational
Definition: rational.h:13
impeller::RectanglePacker::Factory
static std::shared_ptr< RectanglePacker > Factory(int width, int height)
Return an empty packer with area specified by width and height.
Definition: rectangle_packer.cc:175
impeller::TypographerContext
The graphics context necessary to render text.
Definition: typographer_context.h:23
impeller::TypographerContext::CreateGlyphAtlas
virtual std::shared_ptr< GlyphAtlas > CreateGlyphAtlas(Context &context, GlyphAtlas::Type type, HostBuffer &host_buffer, const std::shared_ptr< GlyphAtlasContext > &atlas_context, const std::vector< std::shared_ptr< TextFrame >> &text_frames) const =0
impeller::TypographerContextSkia::Make
static std::shared_ptr< TypographerContext > Make()
Definition: typographer_context_skia.cc:76
font_glyph_pair.h
NumberNear
bool NumberNear(double a, double b)
Definition: geometry_asserts.h:18
host_buffer.h
lazy_glyph_atlas.h
impeller::testing::TEST
TEST(AllocationSizeTest, CanCreateTypedAllocations)
Definition: allocation_size_unittests.cc:10
impeller::testing::TEST_P
TEST_P(AiksTest, DrawAtlasNoColor)
Definition: aiks_dl_atlas_unittests.cc:59
impeller::testing::CreateGlyphAtlas
static std::shared_ptr< GlyphAtlas > CreateGlyphAtlas(Context &context, const TypographerContext *typographer_context, HostBuffer &data_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::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
impeller
Definition: allocation.cc:12
impeller::PlaygroundBackend::kOpenGLES
@ kOpenGLES
impeller::MakeTextFrameFromTextBlobSkia
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
Definition: text_frame_skia.cc:45
playground_test.h
playground.h
rectangle_packer.h
c
Point c
Definition: round_superellipse_param.cc:315
impeller::Color::Red
static constexpr Color Red()
Definition: color.h:272
impeller::Color::Blue
static constexpr Color Blue()
Definition: color.h:276
impeller::Glyph::index
uint16_t index
Definition: glyph.h:22
impeller::GlyphProperties
Definition: font_glyph_pair.h:19
impeller::GlyphProperties::color
Color color
Definition: font_glyph_pair.h:20
impeller::IPoint16
Definition: rectangle_packer.h:16
impeller::IPoint16::y
int16_t y() const
Definition: rectangle_packer.h:18
impeller::IPoint16::x
int16_t x() const
Definition: rectangle_packer.h:17
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::ScaledFont
A font and a scale. Used as a key that represents a typeface within a glyph atlas.
Definition: font_glyph_pair.h:36
impeller::SubpixelGlyph
A glyph and its subpixel position.
Definition: font_glyph_pair.h:85
impeller::SubpixelGlyph::glyph
Glyph glyph
Definition: font_glyph_pair.h:86
impeller::TRect< Scalar >
impeller::TSize
Definition: size.h:25
text_frame_skia.h
typographer_context_skia.h