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