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