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"
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 
31 
32 static std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
33  Context& context,
34  const TypographerContext* typographer_context,
35  HostBuffer& host_buffer,
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,
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,
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 
139 
140  lazy_atlas.AddTextFrame(frame, 1.0f, {0, 0}, Matrix(), {});
141 
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,
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,
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,
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  "œ∑´®†¥¨ˆøπ““‘‘åß∂ƒ©˙∆˚¬…æ≈ç√∫˜µ≤≥≥≥≥÷¡™£¢∞§¶•ªº–≠⁄€‹›fifl‡°·‚—±Œ„´‰Á¨Ø∏”’/"
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,
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,
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 = {
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,
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,
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)
GLenum type
To do anything rendering related with Impeller, you need a context.
Definition: context.h:46
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
const std::shared_ptr< GlyphAtlas > & CreateOrGetGlyphAtlas(Context &context, HostBuffer &host_buffer, GlyphAtlas::Type type) const
void AddTextFrame(const std::shared_ptr< TextFrame > &frame, Scalar scale, Point offset, const Matrix &transform, std::optional< GlyphProperties > properties)
static std::shared_ptr< RectanglePacker > Factory(int width, int height)
Return an empty packer with area specified by width and height.
The graphics context necessary to render text.
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
static std::shared_ptr< TypographerContext > Make()
bool NumberNear(double a, double b)
TEST(AllocationSizeTest, CanCreateTypedAllocations)
TEST_P(AiksTest, DrawAtlasNoColor)
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)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
float Scalar
Definition: scalar.h:18
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
const Scalar scale
SeparatedVector2 offset
static constexpr Color Red()
Definition: color.h:271
static constexpr Color Blue()
Definition: color.h:275
uint16_t index
Definition: glyph.h:22
int16_t y() const
int16_t x() const
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
A font and a scale. Used as a key that represents a typeface within a glyph atlas.
A glyph and its subpixel position.