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,
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,
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,
93  atlas_context, 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 
139 
140  lazy_atlas.AddTextFrame(frame, Rational(1), {0, 0}, Matrix(), {});
141 
143  SkTextBlob::MakeFromString("😀 ", emoji_font));
144 
145  ASSERT_TRUE(frame->GetAtlasType() == GlyphAtlas::Type::kColorBitmap);
146 
147  lazy_atlas.AddTextFrame(frame, Rational(1), {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,
172  atlas_context, 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,
193  atlas_context, 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,
203  atlas_context, 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  "œ∑´®†¥¨ˆøπ““‘‘åß∂ƒ©˙∆˚¬…æ≈ç√∫˜µ≤≥≥≥≥÷¡™£¢∞§¶•ªº–≠⁄€‹›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(Rational(6 * index, 10), {0, 0}, Matrix(),
231  {});
232  };
233  auto atlas =
234  context->CreateGlyphAtlas(*GetContext(), GlyphAtlas::Type::kAlphaBitmap,
235  *host_buffer, atlas_context, frames);
236  ASSERT_NE(atlas, nullptr);
237  ASSERT_NE(atlas->GetTexture(), nullptr);
238 
239  std::set<uint16_t> unique_glyphs;
240  std::vector<uint16_t> total_glyphs;
241  atlas->IterateGlyphs([&](const ScaledFont& scaled_font,
242  const SubpixelGlyph& glyph, const Rect& rect) {
243  unique_glyphs.insert(glyph.glyph.index);
244  total_glyphs.push_back(glyph.glyph.index);
245  return true;
246  });
247 
248  // These numbers may be different due to subpixel positions.
249  EXPECT_LE(unique_glyphs.size() * size_count, atlas->GetGlyphCount());
250  EXPECT_EQ(total_glyphs.size(), atlas->GetGlyphCount());
251 
252  EXPECT_TRUE(atlas->GetGlyphCount() > 0);
253  EXPECT_TRUE(atlas->GetTexture()->GetSize().width > 0);
254  EXPECT_TRUE(atlas->GetTexture()->GetSize().height > 0);
255 }
256 
257 TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) {
258  auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(),
259  GetContext()->GetIdleWaiter());
260  auto context = TypographerContextSkia::Make();
261  auto atlas_context =
262  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
263  ASSERT_TRUE(context && context->IsValid());
264  SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
265  auto blob = SkTextBlob::MakeFromString("spooky 1", sk_font);
266  ASSERT_TRUE(blob);
267  auto atlas =
268  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
270  atlas_context, MakeTextFrameFromTextBlobSkia(blob));
271  auto old_packer = atlas_context->GetRectPacker();
272 
273  ASSERT_NE(atlas, nullptr);
274  ASSERT_NE(atlas->GetTexture(), nullptr);
275  ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas());
276 
277  auto* first_texture = atlas->GetTexture().get();
278 
279  // Now create a new glyph atlas with a nearly identical blob.
280 
281  auto blob2 = SkTextBlob::MakeFromString("spooky 2", sk_font);
282  auto next_atlas =
283  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
285  atlas_context, MakeTextFrameFromTextBlobSkia(blob2));
286  ASSERT_EQ(atlas, next_atlas);
287  auto* second_texture = next_atlas->GetTexture().get();
288 
289  auto new_packer = atlas_context->GetRectPacker();
290 
291  ASSERT_EQ(second_texture, first_texture);
292  ASSERT_EQ(old_packer, new_packer);
293 }
294 
295 TEST_P(TypographerTest, GlyphColorIsPartOfCacheKey) {
296  auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(),
297  GetContext()->GetIdleWaiter());
298 #if FML_OS_MACOSX
299  auto mapping = flutter::testing::OpenFixtureAsSkData("Apple Color Emoji.ttc");
300 #else
301  auto mapping = flutter::testing::OpenFixtureAsSkData("NotoColorEmoji.ttf");
302 #endif
303  ASSERT_TRUE(mapping);
304  sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
305  SkFont emoji_font(font_mgr->makeFromData(mapping), 50.0);
306 
307  auto context = TypographerContextSkia::Make();
308  auto atlas_context =
309  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kColorBitmap);
310 
311  // Create two frames with the same character and a different color, expect
312  // that it adds a character.
313  auto frame = MakeTextFrameFromTextBlobSkia(
314  SkTextBlob::MakeFromString("😂", emoji_font));
315  auto frame_2 = MakeTextFrameFromTextBlobSkia(
316  SkTextBlob::MakeFromString("😂", emoji_font));
317  std::vector<std::optional<GlyphProperties>> properties = {
320  };
321 
322  auto next_atlas =
323  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
325  atlas_context, {frame, frame_2}, properties);
326 
327  EXPECT_EQ(next_atlas->GetGlyphCount(), 2u);
328 }
329 
330 TEST_P(TypographerTest, GlyphColorIsIgnoredForNonEmojiFonts) {
331  auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(),
332  GetContext()->GetIdleWaiter());
333  sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
334  sk_sp<SkTypeface> typeface =
335  font_mgr->matchFamilyStyle("Arial", SkFontStyle::Normal());
336  SkFont sk_font(typeface, 0.5f);
337 
338  auto context = TypographerContextSkia::Make();
339  auto atlas_context =
340  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kColorBitmap);
341 
342  // Create two frames with the same character and a different color, but as a
343  // non-emoji font the text frame constructor will ignore it.
344  auto frame =
345  MakeTextFrameFromTextBlobSkia(SkTextBlob::MakeFromString("A", sk_font));
346  auto frame_2 =
347  MakeTextFrameFromTextBlobSkia(SkTextBlob::MakeFromString("A", sk_font));
348  std::vector<std::optional<GlyphProperties>> properties = {
349  GlyphProperties{},
350  GlyphProperties{},
351  };
352 
353  auto next_atlas =
354  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
356  atlas_context, {frame, frame_2}, properties);
357 
358  EXPECT_EQ(next_atlas->GetGlyphCount(), 1u);
359 }
360 
361 TEST_P(TypographerTest, RectanglePackerAddsNonoverlapingRectangles) {
362  auto packer = RectanglePacker::Factory(200, 100);
363  ASSERT_NE(packer, nullptr);
364  ASSERT_EQ(packer->PercentFull(), 0);
365 
366  const SkIRect packer_area = SkIRect::MakeXYWH(0, 0, 200, 100);
367 
368  IPoint16 first_output = {-1, -1}; // Fill with sentinel values
369  ASSERT_TRUE(packer->AddRect(20, 20, &first_output));
370  // Make sure the rectangle is placed such that it is inside the bounds of
371  // the packer's area.
372  const SkIRect first_rect =
373  SkIRect::MakeXYWH(first_output.x(), first_output.y(), 20, 20);
374  ASSERT_TRUE(SkIRect::Intersects(packer_area, first_rect));
375 
376  // Initial area was 200 x 100 = 20_000
377  // We added 20x20 = 400. 400 / 20_000 == 0.02 == 2%
378  ASSERT_TRUE(flutter::testing::NumberNear(packer->PercentFull(), 0.02));
379 
380  IPoint16 second_output = {-1, -1};
381  ASSERT_TRUE(packer->AddRect(140, 90, &second_output));
382  const SkIRect second_rect =
383  SkIRect::MakeXYWH(second_output.x(), second_output.y(), 140, 90);
384  // Make sure the rectangle is placed such that it is inside the bounds of
385  // the packer's area but not in the are of the first rectangle.
386  ASSERT_TRUE(SkIRect::Intersects(packer_area, second_rect));
387  ASSERT_FALSE(SkIRect::Intersects(first_rect, second_rect));
388 
389  // We added another 90 x 140 = 12_600 units, now taking us to 13_000
390  // 13_000 / 20_000 == 0.65 == 65%
391  ASSERT_TRUE(flutter::testing::NumberNear(packer->PercentFull(), 0.65));
392 
393  // There's enough area to add this rectangle, but no space big enough for
394  // the 50 units of width.
395  IPoint16 output;
396  ASSERT_FALSE(packer->AddRect(50, 50, &output));
397  // Should be unchanged.
398  ASSERT_TRUE(flutter::testing::NumberNear(packer->PercentFull(), 0.65));
399 
400  packer->Reset();
401  // Should be empty now.
402  ASSERT_EQ(packer->PercentFull(), 0);
403 }
404 
405 TEST(TypographerTest, RectanglePackerFillsRows) {
406  auto skyline = RectanglePacker::Factory(257, 256);
407 
408  // Fill up the first row.
409  IPoint16 loc;
410  for (auto i = 0u; i < 16; i++) {
411  skyline->AddRect(16, 16, &loc);
412  }
413  // Last rectangle still in first row.
414  EXPECT_EQ(loc.x(), 256 - 16);
415  EXPECT_EQ(loc.y(), 0);
416 
417  // Fill up second row.
418  for (auto i = 0u; i < 16; i++) {
419  skyline->AddRect(16, 16, &loc);
420  }
421 
422  EXPECT_EQ(loc.x(), 256 - 16);
423  EXPECT_EQ(loc.y(), 16);
424 }
425 
426 TEST_P(TypographerTest, GlyphAtlasTextureWillGrowTilMaxTextureSize) {
427  if (GetBackend() == PlaygroundBackend::kOpenGLES) {
428  GTEST_SKIP() << "Atlas growth isn't supported for OpenGLES currently.";
429  }
430 
431  auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(),
432  GetContext()->GetIdleWaiter());
433  auto context = TypographerContextSkia::Make();
434  auto atlas_context =
435  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
436  ASSERT_TRUE(context && context->IsValid());
437  SkFont sk_font = flutter::testing::CreateTestFontOfSize(12);
438  auto blob = SkTextBlob::MakeFromString("A", sk_font);
439  ASSERT_TRUE(blob);
440  auto atlas =
441  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
443  atlas_context, MakeTextFrameFromTextBlobSkia(blob));
444  // Continually append new glyphs until the glyph size grows to the maximum.
445  // Note that the sizes here are more or less experimentally determined, but
446  // the important expectation is that the atlas size will shrink again after
447  // growing to the maximum size.
448  constexpr ISize expected_sizes[13] = {
449  {4096, 4096}, //
450  {4096, 4096}, //
451  {4096, 8192}, //
452  {4096, 8192}, //
453  {4096, 8192}, //
454  {4096, 8192}, //
455  {4096, 16384}, //
456  {4096, 16384}, //
457  {4096, 16384}, //
458  {4096, 16384}, //
459  {4096, 16384}, //
460  {4096, 16384}, //
461  {4096, 4096} // Shrinks!
462  };
463 
464  SkFont sk_font_small = flutter::testing::CreateTestFontOfSize(10);
465 
466  for (int i = 0; i < 13; i++) {
467  SkTextBlobBuilder builder;
468 
469  auto add_char = [&](const SkFont& sk_font, char c) {
470  int count = sk_font.countText(&c, 1, SkTextEncoding::kUTF8);
471  auto buffer = builder.allocRunPos(sk_font, count);
472  sk_font.textToGlyphs(&c, 1, SkTextEncoding::kUTF8, buffer.glyphs, count);
473  sk_font.getPos(buffer.glyphs, count, buffer.points(), {0, 0});
474  };
475 
476  SkFont sk_font = flutter::testing::CreateTestFontOfSize(50 + i);
477  add_char(sk_font, 'A');
478  add_char(sk_font_small, 'B');
479  auto blob = builder.make();
480 
481  atlas =
482  CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
484  atlas_context, MakeTextFrameFromTextBlobSkia(blob));
485  ASSERT_TRUE(!!atlas);
486  EXPECT_EQ(atlas->GetTexture()->GetTextureDescriptor().size,
487  expected_sizes[i]);
488  }
489 
490  // The final atlas should contain both the "A" glyph (which was not present
491  // in the previous atlas) and the "B" glyph (which existed in the previous
492  // atlas).
493  ASSERT_EQ(atlas->GetGlyphCount(), 2u);
494 }
495 
496 TEST_P(TypographerTest, TextFrameInitialBoundsArePlaceholder) {
497  SkFont font = flutter::testing::CreateTestFontOfSize(12);
498  auto blob = SkTextBlob::MakeFromString(
499  "the quick brown fox jumped over the lazy dog.", font);
500  ASSERT_TRUE(blob);
501  auto frame = MakeTextFrameFromTextBlobSkia(blob);
502 
503  EXPECT_FALSE(frame->IsFrameComplete());
504 
505  auto context = TypographerContextSkia::Make();
506  auto atlas_context =
507  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
508  auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(),
509  GetContext()->GetIdleWaiter());
510 
511  auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
513  /*scale=*/Rational(1), atlas_context, frame);
514 
515  // The glyph position in the atlas was not known when this value
516  // was recorded. It is marked as a placeholder.
517  EXPECT_TRUE(frame->IsFrameComplete());
518  EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
519 
520  atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
522  /*scale=*/Rational(1), atlas_context, frame);
523 
524  // The second time the glyph is rendered, the bounds are correcly known.
525  EXPECT_TRUE(frame->IsFrameComplete());
526  EXPECT_FALSE(frame->GetFrameBounds(0).is_placeholder);
527 }
528 
529 TEST_P(TypographerTest, TextFrameInvalidationWithScale) {
530  SkFont font = flutter::testing::CreateTestFontOfSize(12);
531  auto blob = SkTextBlob::MakeFromString(
532  "the quick brown fox jumped over the lazy dog.", font);
533  ASSERT_TRUE(blob);
534  auto frame = MakeTextFrameFromTextBlobSkia(blob);
535 
536  EXPECT_FALSE(frame->IsFrameComplete());
537 
538  auto context = TypographerContextSkia::Make();
539  auto atlas_context =
540  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
541  auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(),
542  GetContext()->GetIdleWaiter());
543 
544  auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
546  /*scale=*/Rational(1), atlas_context, frame);
547 
548  // The glyph position in the atlas was not known when this value
549  // was recorded. It is marked as a placeholder.
550  EXPECT_TRUE(frame->IsFrameComplete());
551  EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
552 
553  // Change the scale and the glyph data will still be a placeholder, as the
554  // old data is no longer valid.
555  atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
557  /*scale=*/Rational(2), atlas_context, frame);
558 
559  // The second time the glyph is rendered, the bounds are correcly known.
560  EXPECT_TRUE(frame->IsFrameComplete());
561  EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
562 }
563 
564 TEST_P(TypographerTest, TextFrameAtlasGenerationTracksState) {
565  SkFont font = flutter::testing::CreateTestFontOfSize(12);
566  auto blob = SkTextBlob::MakeFromString(
567  "the quick brown fox jumped over the lazy dog.", font);
568  ASSERT_TRUE(blob);
569  auto frame = MakeTextFrameFromTextBlobSkia(blob);
570 
571  EXPECT_FALSE(frame->IsFrameComplete());
572 
573  auto context = TypographerContextSkia::Make();
574  auto atlas_context =
575  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
576  auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(),
577  GetContext()->GetIdleWaiter());
578 
579  auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
581  /*scale=*/Rational(1), atlas_context, frame);
582 
583  // The glyph position in the atlas was not known when this value
584  // was recorded. It is marked as a placeholder.
585  EXPECT_TRUE(frame->IsFrameComplete());
586  EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
587  if (GetBackend() == PlaygroundBackend::kOpenGLES) {
588  // OpenGLES must always increase the atlas backend if the texture grows.
589  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
590  } else {
591  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
592  }
593 
594  atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
596  /*scale=*/Rational(1), atlas_context, frame);
597 
598  // The second time the glyph is rendered, the bounds are correcly known.
599  EXPECT_TRUE(frame->IsFrameComplete());
600  EXPECT_FALSE(frame->GetFrameBounds(0).is_placeholder);
601  if (GetBackend() == PlaygroundBackend::kOpenGLES) {
602  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
603  } else {
604  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
605  }
606 
607  // Force increase the generation.
608  atlas_context->GetGlyphAtlas()->SetAtlasGeneration(2u);
609  atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
611  /*scale=*/Rational(1), atlas_context, frame);
612 
613  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 2u);
614 }
615 
616 TEST_P(TypographerTest, InvalidAtlasForcesRepopulation) {
617  SkFont font = flutter::testing::CreateTestFontOfSize(12);
618  auto blob = SkTextBlob::MakeFromString(
619  "the quick brown fox jumped over the lazy dog.", font);
620  ASSERT_TRUE(blob);
621  auto frame = MakeTextFrameFromTextBlobSkia(blob);
622 
623  EXPECT_FALSE(frame->IsFrameComplete());
624 
625  auto context = TypographerContextSkia::Make();
626  auto atlas_context =
627  context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
628  auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(),
629  GetContext()->GetIdleWaiter());
630 
631  auto atlas = CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
633  /*scale=*/Rational(1), atlas_context, frame);
634 
635  // The glyph position in the atlas was not known when this value
636  // was recorded. It is marked as a placeholder.
637  EXPECT_TRUE(frame->IsFrameComplete());
638  EXPECT_TRUE(frame->GetFrameBounds(0).is_placeholder);
639  if (GetBackend() == PlaygroundBackend::kOpenGLES) {
640  // OpenGLES must always increase the atlas backend if the texture grows.
641  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 1u);
642  } else {
643  EXPECT_EQ(frame->GetAtlasGenerationAndID().first, 0u);
644  }
645 
646  auto second_context = TypographerContextSkia::Make();
647  auto second_atlas_context =
648  second_context->CreateGlyphAtlasContext(GlyphAtlas::Type::kAlphaBitmap);
649 
650  EXPECT_FALSE(second_atlas_context->GetGlyphAtlas()->IsValid());
651 
652  atlas = CreateGlyphAtlas(*GetContext(), second_context.get(), *host_buffer,
654  /*scale=*/Rational(1), second_atlas_context, frame);
655 
656  EXPECT_TRUE(second_atlas_context->GetGlyphAtlas()->IsValid());
657 }
658 
659 } // namespace testing
660 } // namespace impeller
661 
662 // NOLINTEND(bugprone-unchecked-optional-access)
GLenum type
To do anything rendering related with Impeller, you need a context.
Definition: context.h:47
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, Rational 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)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
static std::shared_ptr< GlyphAtlas > CreateGlyphAtlas(Context &context, const TypographerContext *typographer_context, HostBuffer &host_buffer, GlyphAtlas::Type type, Rational scale, const std::shared_ptr< GlyphAtlasContext > &atlas_context, const std::shared_ptr< TextFrame > &frame)
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
const Scalar scale
SeparatedVector2 offset
static constexpr Color Red()
Definition: color.h:272
static constexpr Color Blue()
Definition: color.h:276
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.