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