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/testing/testing.h"
11 #include "third_party/skia/include/core/SkData.h"
12 #include "third_party/skia/include/core/SkFontMgr.h"
13 #include "third_party/skia/include/core/SkRect.h"
14 #include "third_party/skia/include/core/SkTextBlob.h"
15 
16 // TODO(zanderso): https://github.com/flutter/flutter/issues/127701
17 // NOLINTBEGIN(bugprone-unchecked-optional-access)
18 
19 namespace impeller {
20 namespace testing {
21 
24 
25 static std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
26  Context& context,
27  const TypographerContext* typographer_context,
28  GlyphAtlas::Type type,
29  Scalar scale,
30  const std::shared_ptr<GlyphAtlasContext>& atlas_context,
31  const TextFrame& frame) {
32  FontGlyphMap font_glyph_map;
33  frame.CollectUniqueFontGlyphPairs(font_glyph_map, scale);
34  return typographer_context->CreateGlyphAtlas(context, type, atlas_context,
35  font_glyph_map);
36 }
37 
38 TEST_P(TypographerTest, CanConvertTextBlob) {
39  SkFont font;
40  auto blob = SkTextBlob::MakeFromString(
41  "the quick brown fox jumped over the lazy dog.", font);
42  ASSERT_TRUE(blob);
43  auto frame = MakeTextFrameFromTextBlobSkia(blob);
44  ASSERT_EQ(frame->GetRunCount(), 1u);
45  for (const auto& run : frame->GetRuns()) {
46  ASSERT_TRUE(run.IsValid());
47  ASSERT_EQ(run.GetGlyphCount(), 45u);
48  }
49 }
50 
51 TEST_P(TypographerTest, CanCreateRenderContext) {
52  auto context = TypographerContextSkia::Make();
53  ASSERT_TRUE(context && context->IsValid());
54 }
55 
56 TEST_P(TypographerTest, CanCreateGlyphAtlas) {
57  auto context = TypographerContextSkia::Make();
58  auto atlas_context = context->CreateGlyphAtlasContext();
59  ASSERT_TRUE(context && context->IsValid());
60  SkFont sk_font;
61  auto blob = SkTextBlob::MakeFromString("hello", sk_font);
62  ASSERT_TRUE(blob);
63  auto atlas = CreateGlyphAtlas(
64  *GetContext(), context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
65  atlas_context, *MakeTextFrameFromTextBlobSkia(blob));
66  ASSERT_NE(atlas, nullptr);
67  ASSERT_NE(atlas->GetTexture(), nullptr);
68  ASSERT_EQ(atlas->GetType(), GlyphAtlas::Type::kAlphaBitmap);
69  ASSERT_EQ(atlas->GetGlyphCount(), 4llu);
70 
71  std::optional<impeller::ScaledFont> first_scaled_font;
72  std::optional<impeller::Glyph> first_glyph;
73  Rect first_rect;
74  atlas->IterateGlyphs([&](const ScaledFont& scaled_font, const Glyph& glyph,
75  const Rect& rect) -> bool {
76  first_scaled_font = scaled_font;
77  first_glyph = glyph;
78  first_rect = rect;
79  return false;
80  });
81 
82  ASSERT_TRUE(first_scaled_font.has_value());
83  ASSERT_TRUE(atlas
84  ->FindFontGlyphBounds(
85  {first_scaled_font.value(), first_glyph.value()})
86  .has_value());
87 }
88 
89 static sk_sp<SkData> OpenFixtureAsSkData(const char* fixture_name) {
90  auto mapping = flutter::testing::OpenFixtureAsMapping(fixture_name);
91  if (!mapping) {
92  return nullptr;
93  }
94  auto data = SkData::MakeWithProc(
95  mapping->GetMapping(), mapping->GetSize(),
96  [](const void* ptr, void* context) {
97  delete reinterpret_cast<fml::Mapping*>(context);
98  },
99  mapping.get());
100  mapping.release();
101  return data;
102 }
103 
104 TEST_P(TypographerTest, LazyAtlasTracksColor) {
105 #if FML_OS_MACOSX
106  auto mapping = OpenFixtureAsSkData("Apple Color Emoji.ttc");
107 #else
108  auto mapping = OpenFixtureAsSkData("NotoColorEmoji.ttf");
109 #endif
110  ASSERT_TRUE(mapping);
111  SkFont emoji_font(SkTypeface::MakeFromData(mapping), 50.0);
112  SkFont sk_font;
113 
114  auto blob = SkTextBlob::MakeFromString("hello", sk_font);
115  ASSERT_TRUE(blob);
116  auto frame = MakeTextFrameFromTextBlobSkia(blob);
117 
118  ASSERT_FALSE(frame->GetAtlasType() == GlyphAtlas::Type::kColorBitmap);
119 
121 
122  lazy_atlas.AddTextFrame(*frame, 1.0f);
123 
125  SkTextBlob::MakeFromString("😀 ", emoji_font));
126 
127  ASSERT_TRUE(frame->GetAtlasType() == GlyphAtlas::Type::kColorBitmap);
128 
129  lazy_atlas.AddTextFrame(*frame, 1.0f);
130 
131  // Creates different atlases for color and alpha bitmap.
132  auto color_atlas = lazy_atlas.CreateOrGetGlyphAtlas(
133  *GetContext(), GlyphAtlas::Type::kColorBitmap);
134 
135  auto bitmap_atlas = lazy_atlas.CreateOrGetGlyphAtlas(
136  *GetContext(), GlyphAtlas::Type::kAlphaBitmap);
137 
138  ASSERT_FALSE(color_atlas == bitmap_atlas);
139 }
140 
141 TEST_P(TypographerTest, GlyphAtlasWithOddUniqueGlyphSize) {
142  auto context = TypographerContextSkia::Make();
143  auto atlas_context = context->CreateGlyphAtlasContext();
144  ASSERT_TRUE(context && context->IsValid());
145  SkFont sk_font;
146  auto blob = SkTextBlob::MakeFromString("AGH", sk_font);
147  ASSERT_TRUE(blob);
148  auto atlas = CreateGlyphAtlas(
149  *GetContext(), context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
150  atlas_context, *MakeTextFrameFromTextBlobSkia(blob));
151  ASSERT_NE(atlas, nullptr);
152  ASSERT_NE(atlas->GetTexture(), nullptr);
153 
154  ASSERT_EQ(atlas->GetTexture()->GetSize().width,
155  atlas->GetTexture()->GetSize().height);
156 }
157 
158 TEST_P(TypographerTest, GlyphAtlasIsRecycledIfUnchanged) {
159  auto context = TypographerContextSkia::Make();
160  auto atlas_context = context->CreateGlyphAtlasContext();
161  ASSERT_TRUE(context && context->IsValid());
162  SkFont sk_font;
163  auto blob = SkTextBlob::MakeFromString("spooky skellingtons", sk_font);
164  ASSERT_TRUE(blob);
165  auto atlas = CreateGlyphAtlas(
166  *GetContext(), context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
167  atlas_context, *MakeTextFrameFromTextBlobSkia(blob));
168  ASSERT_NE(atlas, nullptr);
169  ASSERT_NE(atlas->GetTexture(), nullptr);
170  ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas());
171 
172  // now attempt to re-create an atlas with the same text blob.
173 
174  auto next_atlas = CreateGlyphAtlas(
175  *GetContext(), context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
176  atlas_context, *MakeTextFrameFromTextBlobSkia(blob));
177  ASSERT_EQ(atlas, next_atlas);
178  ASSERT_EQ(atlas_context->GetGlyphAtlas(), atlas);
179 }
180 
181 TEST_P(TypographerTest, GlyphAtlasWithLotsOfdUniqueGlyphSize) {
182  auto context = TypographerContextSkia::Make();
183  auto atlas_context = context->CreateGlyphAtlasContext();
184  ASSERT_TRUE(context && context->IsValid());
185 
186  const char* test_string =
187  "QWERTYUIOPASDFGHJKLZXCVBNMqewrtyuiopasdfghjklzxcvbnm,.<>[]{};':"
188  "2134567890-=!@#$%^&*()_+"
189  "œ∑´®†¥¨ˆøπ““‘‘åß∂ƒ©˙∆˚¬…æ≈ç√∫˜µ≤≥≥≥≥÷¡™£¢∞§¶•ªº–≠⁄€‹›fifl‡°·‚—±Œ„´‰Á¨Ø∏”’/"
190  "* Í˝ */¸˛Ç◊ı˜Â¯˘¿";
191 
192  SkFont sk_font;
193  auto blob = SkTextBlob::MakeFromString(test_string, sk_font);
194  ASSERT_TRUE(blob);
195 
196  FontGlyphMap font_glyph_map;
197  size_t size_count = 8;
198  for (size_t index = 0; index < size_count; index += 1) {
199  MakeTextFrameFromTextBlobSkia(blob)->CollectUniqueFontGlyphPairs(
200  font_glyph_map, 0.6 * index);
201  };
202  auto atlas =
203  context->CreateGlyphAtlas(*GetContext(), GlyphAtlas::Type::kAlphaBitmap,
204  std::move(atlas_context), font_glyph_map);
205  ASSERT_NE(atlas, nullptr);
206  ASSERT_NE(atlas->GetTexture(), nullptr);
207 
208  std::set<uint16_t> unique_glyphs;
209  std::vector<uint16_t> total_glyphs;
210  atlas->IterateGlyphs(
211  [&](const ScaledFont& scaled_font, const Glyph& glyph, const Rect& rect) {
212  unique_glyphs.insert(glyph.index);
213  total_glyphs.push_back(glyph.index);
214  return true;
215  });
216 
217  EXPECT_EQ(unique_glyphs.size() * size_count, atlas->GetGlyphCount());
218  EXPECT_EQ(total_glyphs.size(), atlas->GetGlyphCount());
219 
220  EXPECT_TRUE(atlas->GetGlyphCount() > 0);
221  EXPECT_TRUE(atlas->GetTexture()->GetSize().width > 0);
222  EXPECT_TRUE(atlas->GetTexture()->GetSize().height > 0);
223 }
224 
225 TEST_P(TypographerTest, GlyphAtlasTextureIsRecycledIfUnchanged) {
226  auto context = TypographerContextSkia::Make();
227  auto atlas_context = context->CreateGlyphAtlasContext();
228  ASSERT_TRUE(context && context->IsValid());
229  SkFont sk_font;
230  auto blob = SkTextBlob::MakeFromString("spooky 1", sk_font);
231  ASSERT_TRUE(blob);
232  auto atlas = CreateGlyphAtlas(
233  *GetContext(), context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
234  atlas_context, *MakeTextFrameFromTextBlobSkia(blob));
235  auto old_packer = atlas_context->GetRectPacker();
236 
237  ASSERT_NE(atlas, nullptr);
238  ASSERT_NE(atlas->GetTexture(), nullptr);
239  ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas());
240 
241  auto* first_texture = atlas->GetTexture().get();
242 
243  // Now create a new glyph atlas with a nearly identical blob.
244 
245  auto blob2 = SkTextBlob::MakeFromString("spooky 2", sk_font);
246  auto next_atlas = CreateGlyphAtlas(
247  *GetContext(), context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
248  atlas_context, *MakeTextFrameFromTextBlobSkia(blob2));
249  ASSERT_EQ(atlas, next_atlas);
250  auto* second_texture = next_atlas->GetTexture().get();
251 
252  auto new_packer = atlas_context->GetRectPacker();
253 
254  ASSERT_EQ(second_texture, first_texture);
255  ASSERT_EQ(old_packer, new_packer);
256 }
257 
258 TEST_P(TypographerTest, GlyphAtlasTextureIsRecreatedIfTypeChanges) {
259  auto context = TypographerContextSkia::Make();
260  auto atlas_context = context->CreateGlyphAtlasContext();
261  ASSERT_TRUE(context && context->IsValid());
262  SkFont sk_font;
263  auto blob = SkTextBlob::MakeFromString("spooky 1", sk_font);
264  ASSERT_TRUE(blob);
265  auto atlas = CreateGlyphAtlas(
266  *GetContext(), context.get(), GlyphAtlas::Type::kAlphaBitmap, 1.0f,
267  atlas_context, *MakeTextFrameFromTextBlobSkia(blob));
268  auto old_packer = atlas_context->GetRectPacker();
269 
270  ASSERT_NE(atlas, nullptr);
271  ASSERT_NE(atlas->GetTexture(), nullptr);
272  ASSERT_EQ(atlas, atlas_context->GetGlyphAtlas());
273 
274  auto* first_texture = atlas->GetTexture().get();
275 
276  // now create a new glyph atlas with an identical blob,
277  // but change the type.
278 
279  auto blob2 = SkTextBlob::MakeFromString("spooky 1", sk_font);
280  auto next_atlas = CreateGlyphAtlas(
281  *GetContext(), context.get(), GlyphAtlas::Type::kColorBitmap, 1.0f,
282  atlas_context, *MakeTextFrameFromTextBlobSkia(blob2));
283  ASSERT_NE(atlas, next_atlas);
284  auto* second_texture = next_atlas->GetTexture().get();
285 
286  auto new_packer = atlas_context->GetRectPacker();
287 
288  ASSERT_NE(second_texture, first_texture);
289  ASSERT_NE(old_packer, new_packer);
290 }
291 
292 TEST_P(TypographerTest, MaybeHasOverlapping) {
293  sk_sp<SkFontMgr> font_mgr = SkFontMgr::RefDefault();
294  sk_sp<SkTypeface> typeface =
295  font_mgr->matchFamilyStyle("Arial", SkFontStyle::Normal());
296  SkFont sk_font(typeface, 0.5f);
297 
298  auto frame =
299  MakeTextFrameFromTextBlobSkia(SkTextBlob::MakeFromString("1", sk_font));
300  // Single character has no overlapping
301  ASSERT_FALSE(frame->MaybeHasOverlapping());
302 
303  auto frame_2 = MakeTextFrameFromTextBlobSkia(
304  SkTextBlob::MakeFromString("123456789", sk_font));
305  ASSERT_FALSE(frame_2->MaybeHasOverlapping());
306 }
307 
308 TEST_P(TypographerTest, RectanglePackerAddsNonoverlapingRectangles) {
309  auto packer = RectanglePacker::Factory(200, 100);
310  ASSERT_NE(packer, nullptr);
311  ASSERT_EQ(packer->percentFull(), 0);
312 
313  const SkIRect packer_area = SkIRect::MakeXYWH(0, 0, 200, 100);
314 
315  IPoint16 first_output = {-1, -1}; // Fill with sentinel values
316  ASSERT_TRUE(packer->addRect(20, 20, &first_output));
317  // Make sure the rectangle is placed such that it is inside the bounds of
318  // the packer's area.
319  const SkIRect first_rect =
320  SkIRect::MakeXYWH(first_output.x(), first_output.y(), 20, 20);
321  ASSERT_TRUE(SkIRect::Intersects(packer_area, first_rect));
322 
323  // Initial area was 200 x 100 = 20_000
324  // We added 20x20 = 400. 400 / 20_000 == 0.02 == 2%
325  ASSERT_TRUE(flutter::testing::NumberNear(packer->percentFull(), 0.02));
326 
327  IPoint16 second_output = {-1, -1};
328  ASSERT_TRUE(packer->addRect(140, 90, &second_output));
329  const SkIRect second_rect =
330  SkIRect::MakeXYWH(second_output.x(), second_output.y(), 140, 90);
331  // Make sure the rectangle is placed such that it is inside the bounds of
332  // the packer's area but not in the are of the first rectangle.
333  ASSERT_TRUE(SkIRect::Intersects(packer_area, second_rect));
334  ASSERT_FALSE(SkIRect::Intersects(first_rect, second_rect));
335 
336  // We added another 90 x 140 = 12_600 units, now taking us to 13_000
337  // 13_000 / 20_000 == 0.65 == 65%
338  ASSERT_TRUE(flutter::testing::NumberNear(packer->percentFull(), 0.65));
339 
340  // There's enough area to add this rectangle, but no space big enough for
341  // the 50 units of width.
342  IPoint16 output;
343  ASSERT_FALSE(packer->addRect(50, 50, &output));
344  // Should be unchanged.
345  ASSERT_TRUE(flutter::testing::NumberNear(packer->percentFull(), 0.65));
346 
347  packer->reset();
348  // Should be empty now.
349  ASSERT_EQ(packer->percentFull(), 0);
350 }
351 
352 } // namespace testing
353 } // namespace impeller
354 
355 // NOLINTEND(bugprone-unchecked-optional-access)
impeller::GlyphAtlas::Type::kColorBitmap
@ kColorBitmap
NumberNear
bool NumberNear(double a, double b)
Definition: geometry_asserts.h:17
lazy_glyph_atlas.h
impeller::Scalar
float Scalar
Definition: scalar.h:15
impeller::TypographerContext
The graphics context necessary to render text.
Definition: typographer_context.h:22
impeller::GlyphAtlas::Type::kAlphaBitmap
@ kAlphaBitmap
impeller::TextFrame
Represents a collection of shaped text runs.
Definition: text_frame.h:19
impeller::LazyGlyphAtlas::AddTextFrame
void AddTextFrame(const TextFrame &frame, Scalar scale)
Definition: lazy_glyph_atlas.cc:26
impeller::testing::OpenFixtureAsSkData
static sk_sp< SkData > OpenFixtureAsSkData(const char *fixture_name)
Definition: aiks_unittests.cc:1282
impeller::TextFrame::CollectUniqueFontGlyphPairs
void CollectUniqueFontGlyphPairs(FontGlyphMap &glyph_map, Scalar scale) const
Definition: text_frame.cc:70
impeller::FontGlyphMap
std::unordered_map< ScaledFont, std::unordered_set< Glyph > > FontGlyphMap
Definition: font_glyph_pair.h:28
impeller::IPoint16::x
int16_t x() const
Definition: rectangle_packer.h:14
impeller::IPoint16::y
int16_t y() const
Definition: rectangle_packer.h:15
typographer_context_skia.h
impeller::LazyGlyphAtlas::CreateOrGetGlyphAtlas
std::shared_ptr< GlyphAtlas > CreateOrGetGlyphAtlas(Context &context, GlyphAtlas::Type type) const
Definition: lazy_glyph_atlas.cc:41
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
text_frame_skia.h
impeller::testing::TEST_P
TEST_P(AiksTest, RotateColorFilteredPath)
Definition: aiks_unittests.cc:56
impeller::Glyph
The glyph index in the typeface.
Definition: glyph.h:19
impeller::TypographerContext::CreateGlyphAtlas
virtual std::shared_ptr< GlyphAtlas > CreateGlyphAtlas(Context &context, GlyphAtlas::Type type, std::shared_ptr< GlyphAtlasContext > atlas_context, const FontGlyphMap &font_glyph_map) const =0
rectangle_packer.h
impeller::GlyphAtlas::Type
Type
Describes how the glyphs are represented in the texture.
Definition: glyph_atlas.h:32
impeller::Glyph::index
uint16_t index
Definition: glyph.h:25
impeller::MakeTextFrameFromTextBlobSkia
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
Definition: text_frame_skia.cc:41
impeller::RectanglePacker::Factory
static RectanglePacker * Factory(int width, int height)
Return an empty packer with area specified by width and height.
Definition: rectangle_packer.cc:169
impeller::Context
To do anything rendering related with Impeller, you need a context.
Definition: context.h:47
impeller::IPoint16
Definition: rectangle_packer.h:13
impeller::PlaygroundTest
Definition: playground_test.h:22
impeller::testing::CreateGlyphAtlas
static std::shared_ptr< GlyphAtlas > CreateGlyphAtlas(Context &context, const TypographerContext *typographer_context, GlyphAtlas::Type type, Scalar scale, const std::shared_ptr< GlyphAtlasContext > &atlas_context, const TextFrame &frame)
Definition: typographer_unittests.cc:25
impeller::LazyGlyphAtlas
Definition: lazy_glyph_atlas.h:17
impeller
Definition: aiks_context.cc:10
playground_test.h
impeller::TypographerContextSkia::Make
static std::shared_ptr< TypographerContext > Make()
Definition: typographer_context_skia.cc:30
impeller::TRect< Scalar >
impeller::ScaledFont
A font and a scale. Used as a key that represents a typeface within a glyph atlas.
Definition: font_glyph_pair.h:23