Flutter Engine
The Flutter Engine
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
FontMgrAndroidParserTest.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
11#include "include/core/SkFont.h"
28#include "src/core/SkOSFile.h"
29#include "src/core/SkTHash.h"
31#include "tests/Test.h"
32#include "tools/Resources.h"
34
35#ifdef SK_TYPEFACE_FACTORY_FONTATIONS
37#endif
39
40#include <algorithm>
41#include <climits>
42#include <cmath>
43#include <cstdint>
44#include <cstdio>
45#include <memory>
46#include <string>
47
48DECLARE_bool(verboseFontMgr)
49
50int CountFallbacks(SkTDArray<FontFamily*> fontFamilies) {
51 int countOfFallbackFonts = 0;
52 for (int i = 0; i < fontFamilies.size(); i++) {
53 if (fontFamilies[i]->fIsFallbackFont) {
54 countOfFallbackFonts++;
55 }
56 }
57 return countOfFallbackFonts;
58}
59
60//https://tools.ietf.org/html/rfc5234#appendix-B.1
61static bool isALPHA(int c) {
62 return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
63}
64
65//https://tools.ietf.org/html/rfc5234#appendix-B.1
66static bool isDIGIT(int c) {
67 return ('0' <= c && c <= '9');
68}
69
70static void ValidateLoadedFonts(SkTDArray<FontFamily*> fontFamilies, const char* firstExpectedFile,
72 REPORTER_ASSERT(reporter, fontFamilies[0]->fNames.size() == 5);
73 REPORTER_ASSERT(reporter, !strcmp(fontFamilies[0]->fNames[0].c_str(), "sans-serif"));
75 !strcmp(fontFamilies[0]->fFonts[0].fFileName.c_str(), firstExpectedFile));
76 REPORTER_ASSERT(reporter, !fontFamilies[0]->fIsFallbackFont);
77
78 // Check that the languages are all sane.
79 for (const auto& fontFamily : fontFamilies) {
80 for (const auto& lang : fontFamily->fLanguages) {
81 const SkString& langString = lang.getTag();
82 for (size_t i = 0; i < langString.size(); ++i) {
83 int c = langString[i];
84 REPORTER_ASSERT(reporter, isALPHA(c) || isDIGIT(c) || '-' == c);
85 }
86 }
87 }
88
89 // All file names in the test configuration files start with a capital letter.
90 // This is not a general requirement, but it is true of all the test configuration data.
91 // Verifying ensures the filenames have been read sanely and have not been 'sliced'.
92 for (int i = 0; i < fontFamilies.size(); ++i) {
93 FontFamily& family = *fontFamilies[i];
94 for (int j = 0; j < family.fFonts.size(); ++j) {
95 FontFileInfo& file = family.fFonts[j];
96 REPORTER_ASSERT(reporter, !file.fFileName.isEmpty() &&
97 file.fFileName[0] >= 'A' &&
98 file.fFileName[0] <= 'Z');
99 }
100 }
101}
102
103static void DumpFiles(const FontFamily& fontFamily) {
104 for (int j = 0; j < fontFamily.fFonts.size(); ++j) {
105 const FontFileInfo& ffi = fontFamily.fFonts[j];
106 SkDebugf(" file (%d) %s#%d", ffi.fWeight, ffi.fFileName.c_str(), ffi.fIndex);
107 for (const auto& coordinate : ffi.fVariationDesignPosition) {
108 SkDebugf(" @'%c%c%c%c'=%f",
109 (char)((coordinate.axis >> 24) & 0xFF),
110 (char)((coordinate.axis >> 16) & 0xFF),
111 (char)((coordinate.axis >> 8) & 0xFF),
112 (char)((coordinate.axis) & 0xFF),
113 coordinate.value);
114 }
115 SkDebugf("\n");
116 }
117}
118
119static void DumpLoadedFonts(SkTDArray<FontFamily*> fontFamilies, const char* label) {
120 if (!FLAGS_verboseFontMgr) {
121 return;
122 }
123
124 SkDebugf("\n--- Dumping %s\n", label);
125 for (int i = 0; i < fontFamilies.size(); ++i) {
126 SkDebugf("Family %d:\n", i);
127 switch(fontFamilies[i]->fVariant) {
128 case kElegant_FontVariant: SkDebugf(" elegant\n"); break;
129 case kCompact_FontVariant: SkDebugf(" compact\n"); break;
130 default: break;
131 }
132 SkDebugf(" basePath %s\n", fontFamilies[i]->fBasePath.c_str());
133 if (!fontFamilies[i]->fLanguages.empty()) {
134 SkDebugf(" language");
135 for (const auto& lang : fontFamilies[i]->fLanguages) {
136 SkDebugf(" %s", lang.getTag().c_str());
137 }
138 SkDebugf("\n");
139 }
140 for (int j = 0; j < fontFamilies[i]->fNames.size(); ++j) {
141 SkDebugf(" name %s\n", fontFamilies[i]->fNames[j].c_str());
142 }
143 DumpFiles(*fontFamilies[i]);
144 for (const auto& [unused, fallbackFamily] : fontFamilies[i]->fallbackFamilies) {
145 SkDebugf(" Fallback for: %s\n", fallbackFamily->fFallbackFor.c_str());
146 DumpFiles(*fallbackFamily);
147 }
148 }
149 SkDebugf("\n\n");
150}
151
152template <int N, typename T> static double test_parse_fixed_r(skiatest::Reporter* reporter,
153 double low, double high, double inc)
154{
155 double SK_FixedMax_double = nextafter(1 << (sizeof(T) * CHAR_BIT - N - 1), 0.0);
156 double SK_FixedEpsilon_double = (1.0 / (1 << N));
157 double maxError = 0;
158 char buffer[64];
159 for (double f = low; f < high; f += inc) {
160 SkString s;
161 // 'sprintf' formatting as expected depends on the current locale being "C".
162 // We currently expect tests and tools to run in the "C" locale.
163 sprintf(buffer, "%.20f", f);
164 T fix;
165 bool b = parse_fixed<N>(buffer, &fix);
166 if (b) {
167 double f2 = fix * SK_FixedEpsilon_double;
168 double error = fabs(f - f2);
169 REPORTER_ASSERT(reporter, error <= SK_FixedEpsilon_double);
170 maxError = std::max(maxError, error);
171 } else {
172 REPORTER_ASSERT(reporter, f < -SK_FixedMax_double || SK_FixedMax_double < f);
173 }
174 }
175
176 //SkDebugf("maxError: %.20f\n", maxError);
177 return maxError;
178}
179
181 test_parse_fixed_r<27, int32_t>(reporter, -8.1, -7.9, 0.000001);
182 test_parse_fixed_r<27, int32_t>(reporter, -0.1, 0.1, 0.000001);
183 test_parse_fixed_r<27, int32_t>(reporter, 7.9, 8.1, 0.000001);
184 test_parse_fixed_r<16, int32_t>(reporter, -0.125, 0.125, 1.0 / (1 << 19));
185 test_parse_fixed_r<16, int32_t>(reporter, -32768.125, -32766.875, 1.0 / (1 << 17));
186 test_parse_fixed_r<16, int32_t>(reporter, 32766.875, 32768.125, 1.0 / (1 << 17));
187 test_parse_fixed_r<16, int32_t>(reporter, -1.1, 1.1, 0.0001);
188
189 SkFixed fix;
190 REPORTER_ASSERT(reporter, !parse_fixed<27>("-17.1", &fix));
191 REPORTER_ASSERT(reporter, !parse_fixed<16>("32768", &fix));
192 REPORTER_ASSERT(reporter, !parse_fixed<16>("", &fix));
193 REPORTER_ASSERT(reporter, !parse_fixed<16>(".", &fix));
194 REPORTER_ASSERT(reporter, !parse_fixed<16>("123.", &fix));
195 REPORTER_ASSERT(reporter, !parse_fixed<16>("a", &fix));
196 REPORTER_ASSERT(reporter, !parse_fixed<16>(".123a", &fix));
197}
198
199#ifdef SK_TYPEFACE_FACTORY_FONTATIONS
200#define DEF_TEST_FONTATIONS(name, reporter) \
201 DEF_TEST(name##Fontations, reporter) { name(reporter, std::make_unique<SkFontScanner_Fontations>()); }
202#else
203#define DEF_TEST_FONTATIONS(name, reporter)
204#endif
205
206#define DEF_TEST_SCANNERS(name, reporter) \
207 static void name(skiatest::Reporter*, std::unique_ptr<SkFontScanner>); \
208 DEF_TEST(name, reporter) { name(reporter, std::make_unique<SkFontScanner_FreeType>()); } \
209 DEF_TEST_FONTATIONS(name, reporter) \
210 void name(skiatest::Reporter* reporter, std::unique_ptr<SkFontScanner> fs)
211
212DEF_TEST_SCANNERS(FontMgrAndroidParser, reporter) {
214
215 bool resourcesMissing = false;
216
217 SkTDArray<FontFamily*> preV17FontFamilies;
219 SkString("/custom/font/path/"),
220 GetResourcePath("android_fonts/pre_v17/system_fonts.xml").c_str(),
221 GetResourcePath("android_fonts/pre_v17/fallback_fonts.xml").c_str());
222
223 if (preV17FontFamilies.size() > 0) {
224 REPORTER_ASSERT(reporter, preV17FontFamilies.size() == 14);
225 REPORTER_ASSERT(reporter, CountFallbacks(preV17FontFamilies) == 10);
226
227 DumpLoadedFonts(preV17FontFamilies, "pre version 17");
228 ValidateLoadedFonts(preV17FontFamilies, "Roboto-Regular.ttf", reporter);
229 } else {
230 resourcesMissing = true;
231 }
232 for (FontFamily* p : preV17FontFamilies) {
233 delete p;
234 }
235 preV17FontFamilies.reset();
236
237
238 SkTDArray<FontFamily*> v17FontFamilies;
240 SkString("/custom/font/path/"),
241 GetResourcePath("android_fonts/v17/system_fonts.xml").c_str(),
242 GetResourcePath("android_fonts/v17/fallback_fonts.xml").c_str(),
243 GetResourcePath("android_fonts/v17").c_str());
244
245 if (v17FontFamilies.size() > 0) {
246 REPORTER_ASSERT(reporter, v17FontFamilies.size() == 56);
247 REPORTER_ASSERT(reporter, CountFallbacks(v17FontFamilies) == 46);
248
249 DumpLoadedFonts(v17FontFamilies, "version 17");
250 ValidateLoadedFonts(v17FontFamilies, "Roboto-Regular.ttf", reporter);
251 } else {
252 resourcesMissing = true;
253 }
254 for (FontFamily* p : v17FontFamilies) {
255 delete p;
256 }
257 v17FontFamilies.reset();
258
259
260 SkTDArray<FontFamily*> v22FontFamilies;
262 SkString("/custom/font/path/"),
263 GetResourcePath("android_fonts/v22/fonts.xml").c_str(),
264 nullptr);
265
266 if (v22FontFamilies.size() > 0) {
267 REPORTER_ASSERT(reporter, v22FontFamilies.size() == 54);
268 REPORTER_ASSERT(reporter, CountFallbacks(v22FontFamilies) == 42);
269
270 DumpLoadedFonts(v22FontFamilies, "version 22");
271 ValidateLoadedFonts(v22FontFamilies, "Roboto-Thin.ttf", reporter);
272 } else {
273 resourcesMissing = true;
274 }
275 for (FontFamily* p : v22FontFamilies) {
276 delete p;
277 }
278 v22FontFamilies.reset();
279
280 if (resourcesMissing) {
281 SkDebugf("---- Resource files missing for FontConfigParser test\n");
282 }
283}
284
285DEF_TEST_SCANNERS(FontMgrAndroidLegacyMakeTypeface, reporter) {
286 constexpr char fontsXmlFilename[] = "fonts/fonts.xml";
287 SkString basePath = GetResourcePath("fonts/");
288 SkString fontsXml = GetResourcePath(fontsXmlFilename);
289
290 if (!sk_exists(fontsXml.c_str())) {
291 ERRORF(reporter, "file missing: %s\n", fontsXmlFilename);
292 return;
293 }
294
297 custom.fBasePath = basePath.c_str();
298 custom.fFontsXml = fontsXml.c_str();
299 custom.fFallbackFontsXml = nullptr;
300 custom.fIsolated = false;
301
302 sk_sp<SkFontMgr> fm(SkFontMgr_New_Android(&custom, std::move(fs)));
303 sk_sp<SkTypeface> t(fm->legacyMakeTypeface("non-existent-font", SkFontStyle()));
304 REPORTER_ASSERT(reporter, nullptr == t);
305}
306
307static bool bitmap_compare(const SkBitmap& ref, const SkBitmap& test) {
308 for (int y = 0; y < test.height(); ++y) {
309 for (int x = 0; x < test.width(); ++x) {
310 SkColor testColor = test.getColor(x, y);
311 SkColor refColor = ref.getColor(x, y);
312 if (refColor != testColor) {
313 return false;
314 }
315 }
316 }
317 return true;
318}
319
320DEF_TEST_SCANNERS(FontMgrAndroidSystemVariableTypeface, reporter) {
321 constexpr char fontsXmlFilename[] = "fonts/fonts.xml";
322 SkString basePath = GetResourcePath("fonts/");
323 SkString fontsXml = GetResourcePath(fontsXmlFilename);
324
325 if (!sk_exists(fontsXml.c_str())) {
326 ERRORF(reporter, "file missing: %s\n", fontsXmlFilename);
327 return;
328 }
329
332 custom.fBasePath = basePath.c_str();
333 custom.fFontsXml = fontsXml.c_str();
334 custom.fFallbackFontsXml = nullptr;
335 custom.fIsolated = false;
336
337 sk_sp<SkFontMgr> fontMgr(SkFontMgr_New_Android(&custom, std::move(fs)));
338 // "sans-serif" in "fonts/fonts.xml" is "fonts/Distortable.ttf"
339 sk_sp<SkTypeface> typeface(fontMgr->legacyMakeTypeface("sans-serif", SkFontStyle()));
340
341 SkBitmap bitmapStream;
342 bitmapStream.allocN32Pixels(64, 64);
343 SkCanvas canvasStream(bitmapStream);
344 canvasStream.drawColor(SK_ColorWHITE);
345
346 SkBitmap bitmapClone;
347 bitmapClone.allocN32Pixels(64, 64);
348 SkCanvas canvasClone(bitmapClone);
349 canvasStream.drawColor(SK_ColorWHITE);
350
352 paint.setColor(SK_ColorGRAY);
353 paint.setAntiAlias(true);
354 constexpr float kTextSize = 20;
355
356 std::unique_ptr<SkStreamAsset> distortableStream(
357 GetResourceAsStream("fonts/Distortable.ttf"));
358 if (!distortableStream) {
359 return;
360 }
361
362 SkPoint point = SkPoint::Make(20.0f, 20.0f);
363 SkFourByteTag tag = SkSetFourByteTag('w', 'g', 'h', 't');
364
365 for (int i = 0; i < 10; ++i) {
366 SkScalar styleValue =
367 SkDoubleToScalar(0.5 + i * ((2.0 - 0.5) / 10));
369 coordinates[] = {{tag, styleValue}};
371 position = {coordinates, std::size(coordinates)};
372
373 SkFont fontStream(
374 fontMgr->makeFromStream(distortableStream->duplicate(),
375 SkFontArguments().setVariationDesignPosition(position)),
376 kTextSize);
378
379
380 SkFont fontClone(
381 typeface->makeClone(SkFontArguments().setVariationDesignPosition(position)), kTextSize);
383
384 constexpr char text[] = "abc";
385
386 canvasStream.drawColor(SK_ColorWHITE);
387 canvasStream.drawString(text, point.fX, point.fY, fontStream, paint);
388
389 canvasClone.drawColor(SK_ColorWHITE);
390 canvasClone.drawString(text, point.fX, point.fY, fontClone, paint);
391
392 bool success = bitmap_compare(bitmapStream, bitmapClone);
393 REPORTER_ASSERT(reporter, success);
394 }
395}
396
397DEF_TEST_SCANNERS(FontMgrAndroidSystemFallbackFor, reporter) {
398 constexpr char fontsXmlFilename[] = "fonts/fonts.xml";
399 SkString basePath = GetResourcePath("fonts/");
400 SkString fontsXml = GetResourcePath(fontsXmlFilename);
401
402 if (!sk_exists(fontsXml.c_str())) {
403 ERRORF(reporter, "file missing: %s\n", fontsXmlFilename);
404 return;
405 }
406
409 custom.fBasePath = basePath.c_str();
410 custom.fFontsXml = fontsXml.c_str();
411 custom.fFallbackFontsXml = nullptr;
412 custom.fIsolated = false;
413
414 sk_sp<SkFontMgr> fontMgr(SkFontMgr_New_Android(&custom, std::move(fs)));
415 // "sans-serif" in "fonts/fonts.xml" is "fonts/Distortable.ttf", which doesn't have a '!'
416 // but "TestTTC" has a bold font which does have '!' and is marked as fallback for "sans-serif"
417 // and should take precedence over the same font marked as normal weight next to it.
419 "sans-serif", SkFontStyle(), nullptr, 0, '!'));
420
422}
static bool unused
static bool isDIGIT(int c)
static void test_parse_fixed(skiatest::Reporter *reporter)
static void DumpLoadedFonts(SkTDArray< FontFamily * > fontFamilies, const char *label)
DECLARE_bool(verboseFontMgr) int CountFallbacks(SkTDArray< FontFamily * > fontFamilies)
static void DumpFiles(const FontFamily &fontFamily)
#define DEF_TEST_SCANNERS(name, reporter)
static bool isALPHA(int c)
static double test_parse_fixed_r(skiatest::Reporter *reporter, double low, double high, double inc)
static void ValidateLoadedFonts(SkTDArray< FontFamily * > fontFamilies, const char *firstExpectedFile, skiatest::Reporter *reporter)
static bool bitmap_compare(const SkBitmap &ref, const SkBitmap &test)
reporter
Definition: FontMgrTest.cpp:39
std::unique_ptr< SkStreamAsset > GetResourceAsStream(const char *resource, bool useFileStream)
Definition: Resources.cpp:31
SkString GetResourcePath(const char *resource)
Definition: Resources.cpp:23
uint32_t SkColor
Definition: SkColor.h:37
constexpr SkColor SK_ColorGRAY
Definition: SkColor.h:113
constexpr SkColor SK_ColorWHITE
Definition: SkColor.h:122
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
int32_t SkFixed
Definition: SkFixed.h:25
SK_API sk_sp< SkFontMgr > SkFontMgr_New_Android(const SkFontMgr_Android_CustomFonts *custom)
@ kCompact_FontVariant
@ kElegant_FontVariant
bool sk_exists(const char *path, SkFILE_Flags=(SkFILE_Flags) 0)
#define SkDoubleToScalar(x)
Definition: SkScalar.h:64
uint32_t SkFourByteTag
Definition: SkTypes.h:166
static constexpr SkFourByteTag SkSetFourByteTag(char a, char b, char c, char d)
Definition: SkTypes.h:167
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
#define ERRORF(r,...)
Definition: Test.h:293
#define N
Definition: beziers.cpp:19
SkColor getColor(int x, int y) const
Definition: SkBitmap.h:874
void allocN32Pixels(int width, int height, bool isOpaque=false)
Definition: SkBitmap.cpp:232
void drawColor(SkColor color, SkBlendMode mode=SkBlendMode::kSrcOver)
Definition: SkCanvas.h:1182
void drawString(const char str[], SkScalar x, SkScalar y, const SkFont &font, const SkPaint &paint)
Definition: SkCanvas.h:1803
sk_sp< SkTypeface > matchFamilyStyleCharacter(const char familyName[], const SkFontStyle &, const char *bcp47[], int bcp47Count, SkUnichar character) const
Definition: SkFontMgr.cpp:114
sk_sp< SkTypeface > makeFromStream(std::unique_ptr< SkStreamAsset >, int ttcIndex=0) const
Definition: SkFontMgr.cpp:127
sk_sp< SkTypeface > legacyMakeTypeface(const char familyName[], SkFontStyle style) const
Definition: SkFontMgr.cpp:150
static constexpr SkFontStyle Bold()
Definition: SkFontStyle.h:69
Definition: SkFont.h:35
void setEdging(Edging edging)
Definition: SkFont.cpp:121
@ kSubpixelAntiAlias
glyph positioned in pixel using transparency
size_t size() const
Definition: SkString.h:131
const char * c_str() const
Definition: SkString.h:133
int size() const
Definition: SkTDArray.h:138
bool empty() const
Definition: SkTDArray.h:135
void reset()
Definition: SkTDArray.h:171
SkFontStyle fontStyle() const
Definition: SkTypeface.h:55
sk_sp< SkTypeface > makeClone(const SkFontArguments &) const
Definition: SkTypeface.cpp:190
int size() const
Definition: SkTArray.h:421
const Paint & paint
Definition: color_source.cc:38
sk_sp< SkFontMgr > fontMgr
Definition: examples.cpp:32
float SkScalar
Definition: extension.cpp:12
static bool b
struct MyStruct s
const uint8_t uint32_t uint32_t GError ** error
static float max(float r, float g, float b)
Definition: hsl.cpp:49
std::u16string text
double y
double x
void GetCustomFontFamilies(SkTDArray< FontFamily * > &fontFamilies, const SkString &basePath, const char *fontsXml, const char *fallbackFontsXml, const char *langFallbackFontsDir=nullptr)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
#define T
Definition: precompiler.cc:65
skia_private::TArray< FontFileInfo, true > fFonts
skia_private::TArray< SkFontArguments::VariationPosition::Coordinate, true > fVariationDesignPosition
float fX
x-axis value
Definition: SkPoint_impl.h:164
static constexpr SkPoint Make(float x, float y)
Definition: SkPoint_impl.h:173
float fY
y-axis value
Definition: SkPoint_impl.h:165