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& data_host_buffer,
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,
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,
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 
141 
142  lazy_atlas.AddTextFrame(frame, Rational(1), {0, 0}, Matrix(), {});
143 
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,
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,
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,
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  "œ∑´®†¥¨ˆøπ““‘‘åß∂ƒ©˙∆˚¬…æ≈ç√∫˜µ≤≥≥≥≥÷¡™£¢∞§¶•ªº–≠⁄€‹›fifl‡°·‚—±Œ„´‰Á¨Ø∏”’/"
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,
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,
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 = {
327  };
328 
329  auto next_atlas =
330  CreateGlyphAtlas(*GetContext(), context.get(), *data_host_buffer,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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)
GLenum type
To do anything rendering related with Impeller, you need a context.
Definition: context.h:65
Type
Describes how the glyphs are represented in the texture.
Definition: glyph_atlas.h:41
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
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)
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)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
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.