1 /*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "minikin/FontFamily.h"
18
19 #include <gtest/gtest.h>
20
21 #include "minikin/LocaleList.h"
22
23 #include "FontTestUtils.h"
24 #include "FreeTypeMinikinFontForTest.h"
25 #include "LocaleListCache.h"
26 #include "MinikinInternal.h"
27
28 namespace minikin {
29
createLocaleList(const std::string & input)30 static const LocaleList& createLocaleList(const std::string& input) {
31 uint32_t localeListId = LocaleListCache::getId(input);
32 return LocaleListCache::getById(localeListId);
33 }
34
createLocale(const std::string & input)35 static Locale createLocale(const std::string& input) {
36 uint32_t localeListId = LocaleListCache::getId(input);
37 return LocaleListCache::getById(localeListId)[0];
38 }
39
createLocaleWithoutICUSanitization(const std::string & input)40 static Locale createLocaleWithoutICUSanitization(const std::string& input) {
41 return Locale(input);
42 }
43
TEST(LocaleTest,basicTests)44 TEST(LocaleTest, basicTests) {
45 Locale defaultLocale;
46 Locale emptyLocale("");
47 Locale english = createLocale("en");
48 Locale french = createLocale("fr");
49 Locale und = createLocale("und");
50 Locale undZsye = createLocale("und-Zsye");
51
52 EXPECT_EQ(english, english);
53 EXPECT_EQ(french, french);
54
55 EXPECT_TRUE(defaultLocale != defaultLocale);
56 EXPECT_TRUE(emptyLocale != emptyLocale);
57 EXPECT_TRUE(defaultLocale != emptyLocale);
58 EXPECT_TRUE(defaultLocale != und);
59 EXPECT_TRUE(emptyLocale != und);
60 EXPECT_TRUE(english != defaultLocale);
61 EXPECT_TRUE(english != emptyLocale);
62 EXPECT_TRUE(english != french);
63 EXPECT_TRUE(english != undZsye);
64 EXPECT_TRUE(und != undZsye);
65 EXPECT_TRUE(english != und);
66 EXPECT_TRUE(createLocale("de-1901") != createLocale("de-1996"));
67
68 EXPECT_TRUE(defaultLocale.isUnsupported());
69 EXPECT_TRUE(emptyLocale.isUnsupported());
70
71 EXPECT_FALSE(english.isUnsupported());
72 EXPECT_FALSE(french.isUnsupported());
73 EXPECT_FALSE(und.isUnsupported());
74 EXPECT_FALSE(undZsye.isUnsupported());
75 }
76
TEST(LocaleTest,getStringTest)77 TEST(LocaleTest, getStringTest) {
78 EXPECT_EQ("en-Latn-US", createLocale("en").getString());
79 EXPECT_EQ("en-Latn-US", createLocale("en-Latn").getString());
80
81 // Capitalized language code or lowercased script should be normalized.
82 EXPECT_EQ("en-Latn-US", createLocale("EN-LATN").getString());
83 EXPECT_EQ("en-Latn-US", createLocale("EN-latn").getString());
84 EXPECT_EQ("en-Latn-US", createLocale("en-latn").getString());
85
86 // Invalid script should be kept.
87 EXPECT_EQ("en-Xyzt-US", createLocale("en-xyzt").getString());
88
89 EXPECT_EQ("en-Latn-US", createLocale("en-Latn-US").getString());
90 EXPECT_EQ("ja-Jpan-JP", createLocale("ja").getString());
91 EXPECT_EQ("zh-Hant-TW", createLocale("zh-TW").getString());
92 EXPECT_EQ("zh-Hant-HK", createLocale("zh-HK").getString());
93 EXPECT_EQ("zh-Hant-MO", createLocale("zh-MO").getString());
94 EXPECT_EQ("zh-Hans-CN", createLocale("zh").getString());
95 EXPECT_EQ("zh-Hans-CN", createLocale("zh-CN").getString());
96 EXPECT_EQ("zh-Hans-SG", createLocale("zh-SG").getString());
97 EXPECT_EQ("und", createLocale("und").getString());
98 EXPECT_EQ("und", createLocale("UND").getString());
99 EXPECT_EQ("und", createLocale("Und").getString());
100 EXPECT_EQ("und-Zsye", createLocale("und-Zsye").getString());
101 EXPECT_EQ("und-Zsye", createLocale("Und-ZSYE").getString());
102 EXPECT_EQ("und-Zsye", createLocale("Und-zsye").getString());
103
104 EXPECT_EQ("es-Latn-419", createLocale("es-Latn-419").getString());
105
106 // Variant
107 EXPECT_EQ("de-Latn-DE", createLocale("de").getString());
108 EXPECT_EQ("de-Latn-DE-1901", createLocale("de-1901").getString());
109 EXPECT_EQ("de-Latn-DE-1996", createLocale("de-DE-1996").getString());
110
111 // Emoji subtag is dropped from getString().
112 EXPECT_EQ("es-Latn-419", createLocale("es-419-u-em-emoji").getString());
113 EXPECT_EQ("es-Latn-419", createLocale("es-Latn-419-u-em-emoji").getString());
114
115 // This is not a necessary desired behavior, just known behavior.
116 EXPECT_EQ("en-Latn-US", createLocale("und-Abcdefgh").getString());
117 }
118
TEST(LocaleTest,testReconstruction)119 TEST(LocaleTest, testReconstruction) {
120 EXPECT_EQ("en", createLocaleWithoutICUSanitization("en").getString());
121 EXPECT_EQ("fil", createLocaleWithoutICUSanitization("fil").getString());
122 EXPECT_EQ("und", createLocaleWithoutICUSanitization("und").getString());
123
124 EXPECT_EQ("en-Latn", createLocaleWithoutICUSanitization("en-Latn").getString());
125 EXPECT_EQ("fil-Taga", createLocaleWithoutICUSanitization("fil-Taga").getString());
126 EXPECT_EQ("und-Zsye", createLocaleWithoutICUSanitization("und-Zsye").getString());
127
128 EXPECT_EQ("en-US", createLocaleWithoutICUSanitization("en-US").getString());
129 EXPECT_EQ("fil-PH", createLocaleWithoutICUSanitization("fil-PH").getString());
130 EXPECT_EQ("es-419", createLocaleWithoutICUSanitization("es-419").getString());
131
132 EXPECT_EQ("en-Latn-US", createLocaleWithoutICUSanitization("en-Latn-US").getString());
133 EXPECT_EQ("fil-Taga-PH", createLocaleWithoutICUSanitization("fil-Taga-PH").getString());
134 EXPECT_EQ("es-Latn-419", createLocaleWithoutICUSanitization("es-Latn-419").getString());
135
136 // Possible minimum/maximum values.
137 EXPECT_EQ("aa", createLocaleWithoutICUSanitization("aa").getString());
138 EXPECT_EQ("zz", createLocaleWithoutICUSanitization("zz").getString());
139 EXPECT_EQ("aa-Aaaa", createLocaleWithoutICUSanitization("aa-Aaaa").getString());
140 EXPECT_EQ("zz-Zzzz", createLocaleWithoutICUSanitization("zz-Zzzz").getString());
141 EXPECT_EQ("aaa-Aaaa-AA", createLocaleWithoutICUSanitization("aaa-Aaaa-AA").getString());
142 EXPECT_EQ("zzz-Zzzz-ZZ", createLocaleWithoutICUSanitization("zzz-Zzzz-ZZ").getString());
143 EXPECT_EQ("aaa-Aaaa-000", createLocaleWithoutICUSanitization("aaa-Aaaa-000").getString());
144 EXPECT_EQ("zzz-Zzzz-999", createLocaleWithoutICUSanitization("zzz-Zzzz-999").getString());
145 }
146
TEST(LocaleTest,ScriptEqualTest)147 TEST(LocaleTest, ScriptEqualTest) {
148 EXPECT_TRUE(createLocale("en").isEqualScript(createLocale("en")));
149 EXPECT_TRUE(createLocale("en-Latn").isEqualScript(createLocale("en")));
150 EXPECT_TRUE(createLocale("jp-Latn").isEqualScript(createLocale("en-Latn")));
151 EXPECT_TRUE(createLocale("en-Jpan").isEqualScript(createLocale("en-Jpan")));
152
153 EXPECT_FALSE(createLocale("en-Jpan").isEqualScript(createLocale("en-Hira")));
154 EXPECT_FALSE(createLocale("en-Jpan").isEqualScript(createLocale("en-Hani")));
155 }
156
TEST(LocaleTest,ScriptMatchTest)157 TEST(LocaleTest, ScriptMatchTest) {
158 const bool SUPPORTED = true;
159 const bool NOT_SUPPORTED = false;
160
161 struct TestCase {
162 const std::string baseScript;
163 const std::string requestedScript;
164 bool isSupported;
165 } testCases[] = {
166 // Same scripts
167 {"en-Latn", "Latn", SUPPORTED},
168 {"ja-Jpan", "Jpan", SUPPORTED},
169 {"ja-Hira", "Hira", SUPPORTED},
170 {"ja-Kana", "Kana", SUPPORTED},
171 {"ja-Hrkt", "Hrkt", SUPPORTED},
172 {"zh-Hans", "Hans", SUPPORTED},
173 {"zh-Hant", "Hant", SUPPORTED},
174 {"zh-Hani", "Hani", SUPPORTED},
175 {"ko-Kore", "Kore", SUPPORTED},
176 {"ko-Hang", "Hang", SUPPORTED},
177 {"zh-Hanb", "Hanb", SUPPORTED},
178
179 // Japanese supports Hiragana, Katakanara, etc.
180 {"ja-Jpan", "Hira", SUPPORTED},
181 {"ja-Jpan", "Kana", SUPPORTED},
182 {"ja-Jpan", "Hrkt", SUPPORTED},
183 {"ja-Hrkt", "Hira", SUPPORTED},
184 {"ja-Hrkt", "Kana", SUPPORTED},
185
186 // Chinese supports Han.
187 {"zh-Hans", "Hani", SUPPORTED},
188 {"zh-Hant", "Hani", SUPPORTED},
189 {"zh-Hanb", "Hani", SUPPORTED},
190
191 // Hanb supports Bopomofo.
192 {"zh-Hanb", "Bopo", SUPPORTED},
193
194 // Korean supports Hangul.
195 {"ko-Kore", "Hang", SUPPORTED},
196
197 // Different scripts
198 {"ja-Jpan", "Latn", NOT_SUPPORTED},
199 {"en-Latn", "Jpan", NOT_SUPPORTED},
200 {"ja-Jpan", "Hant", NOT_SUPPORTED},
201 {"zh-Hant", "Jpan", NOT_SUPPORTED},
202 {"ja-Jpan", "Hans", NOT_SUPPORTED},
203 {"zh-Hans", "Jpan", NOT_SUPPORTED},
204 {"ja-Jpan", "Kore", NOT_SUPPORTED},
205 {"ko-Kore", "Jpan", NOT_SUPPORTED},
206 {"zh-Hans", "Hant", NOT_SUPPORTED},
207 {"zh-Hant", "Hans", NOT_SUPPORTED},
208 {"zh-Hans", "Kore", NOT_SUPPORTED},
209 {"ko-Kore", "Hans", NOT_SUPPORTED},
210 {"zh-Hant", "Kore", NOT_SUPPORTED},
211 {"ko-Kore", "Hant", NOT_SUPPORTED},
212
213 // Hiragana doesn't support Japanese, etc.
214 {"ja-Hira", "Jpan", NOT_SUPPORTED},
215 {"ja-Kana", "Jpan", NOT_SUPPORTED},
216 {"ja-Hrkt", "Jpan", NOT_SUPPORTED},
217 {"ja-Hani", "Jpan", NOT_SUPPORTED},
218 {"ja-Hira", "Hrkt", NOT_SUPPORTED},
219 {"ja-Kana", "Hrkt", NOT_SUPPORTED},
220 {"ja-Hani", "Hrkt", NOT_SUPPORTED},
221 {"ja-Hani", "Hira", NOT_SUPPORTED},
222 {"ja-Hani", "Kana", NOT_SUPPORTED},
223
224 // Kanji doesn't support Chinese, etc.
225 {"zh-Hani", "Hant", NOT_SUPPORTED},
226 {"zh-Hani", "Hans", NOT_SUPPORTED},
227 {"zh-Hani", "Hanb", NOT_SUPPORTED},
228
229 // Hangul doesn't support Korean, etc.
230 {"ko-Hang", "Kore", NOT_SUPPORTED},
231 {"ko-Hani", "Kore", NOT_SUPPORTED},
232 {"ko-Hani", "Hang", NOT_SUPPORTED},
233 {"ko-Hang", "Hani", NOT_SUPPORTED},
234
235 // Han with botomofo doesn't support simplified Chinese, etc.
236 {"zh-Hanb", "Hant", NOT_SUPPORTED},
237 {"zh-Hanb", "Hans", NOT_SUPPORTED},
238 {"zh-Hanb", "Jpan", NOT_SUPPORTED},
239 {"zh-Hanb", "Kore", NOT_SUPPORTED},
240 };
241
242 for (auto testCase : testCases) {
243 hb_script_t script = hb_script_from_iso15924_tag(
244 HB_TAG(testCase.requestedScript[0], testCase.requestedScript[1],
245 testCase.requestedScript[2], testCase.requestedScript[3]));
246 if (testCase.isSupported) {
247 EXPECT_TRUE(createLocale(testCase.baseScript).supportsHbScript(script))
248 << testCase.baseScript << " should support " << testCase.requestedScript;
249 } else {
250 EXPECT_FALSE(createLocale(testCase.baseScript).supportsHbScript(script))
251 << testCase.baseScript << " shouldn't support " << testCase.requestedScript;
252 }
253 }
254 }
255
TEST(LocaleListTest,basicTests)256 TEST(LocaleListTest, basicTests) {
257 LocaleList emptyLocales;
258 EXPECT_EQ(0u, emptyLocales.size());
259
260 Locale english = createLocale("en");
261 const LocaleList& singletonLocales = createLocaleList("en");
262 EXPECT_EQ(1u, singletonLocales.size());
263 EXPECT_EQ(english, singletonLocales[0]);
264
265 Locale french = createLocale("fr");
266 const LocaleList& twoLocales = createLocaleList("en,fr");
267 EXPECT_EQ(2u, twoLocales.size());
268 EXPECT_EQ(english, twoLocales[0]);
269 EXPECT_EQ(french, twoLocales[1]);
270 }
271
TEST(LocaleListTest,unsupportedLocaleuageTests)272 TEST(LocaleListTest, unsupportedLocaleuageTests) {
273 const LocaleList& oneUnsupported = createLocaleList("abcd-example");
274 EXPECT_TRUE(oneUnsupported.empty());
275
276 const LocaleList& twoUnsupporteds = createLocaleList("abcd-example,abcd-example");
277 EXPECT_TRUE(twoUnsupporteds.empty());
278
279 Locale english = createLocale("en");
280 const LocaleList& firstUnsupported = createLocaleList("abcd-example,en");
281 EXPECT_EQ(1u, firstUnsupported.size());
282 EXPECT_EQ(english, firstUnsupported[0]);
283
284 const LocaleList& lastUnsupported = createLocaleList("en,abcd-example");
285 EXPECT_EQ(1u, lastUnsupported.size());
286 EXPECT_EQ(english, lastUnsupported[0]);
287 }
288
TEST(LocaleListTest,repeatedLocaleuageTests)289 TEST(LocaleListTest, repeatedLocaleuageTests) {
290 Locale english = createLocale("en");
291 Locale french = createLocale("fr");
292 Locale canadianFrench = createLocale("fr-CA");
293 Locale englishInLatn = createLocale("en-Latn");
294 ASSERT_TRUE(english == englishInLatn);
295
296 const LocaleList& locales = createLocaleList("en,en-Latn");
297 EXPECT_EQ(1u, locales.size());
298 EXPECT_EQ(english, locales[0]);
299
300 const LocaleList& fr = createLocaleList("fr,fr-FR,fr-Latn-FR");
301 EXPECT_EQ(1u, fr.size());
302 EXPECT_EQ(french, fr[0]);
303
304 // ICU appends FR to fr. The third language is dropped which is same as the first language.
305 const LocaleList& fr2 = createLocaleList("fr,fr-CA,fr-FR");
306 EXPECT_EQ(2u, fr2.size());
307 EXPECT_EQ(french, fr2[0]);
308 EXPECT_EQ(canadianFrench, fr2[1]);
309
310 // The order should be kept.
311 const LocaleList& locales2 = createLocaleList("en,fr,en-Latn");
312 EXPECT_EQ(2u, locales2.size());
313 EXPECT_EQ(english, locales2[0]);
314 EXPECT_EQ(french, locales2[1]);
315 }
316
TEST(LocaleListTest,identifierTest)317 TEST(LocaleListTest, identifierTest) {
318 EXPECT_EQ(createLocale("en-Latn-US"), createLocale("en-Latn-US"));
319 EXPECT_EQ(createLocale("zh-Hans-CN"), createLocale("zh-Hans-CN"));
320 EXPECT_EQ(createLocale("en-Zsye-US"), createLocale("en-Zsye-US"));
321
322 EXPECT_NE(createLocale("en-Latn-US"), createLocale("en-Latn-GB"));
323 EXPECT_NE(createLocale("en-Latn-US"), createLocale("en-Zsye-US"));
324 EXPECT_NE(createLocale("es-Latn-US"), createLocale("en-Latn-US"));
325 EXPECT_NE(createLocale("zh-Hant-HK"), createLocale("zh-Hant-TW"));
326 }
327
TEST(LocaleListTest,undEmojiTests)328 TEST(LocaleListTest, undEmojiTests) {
329 Locale emoji = createLocale("und-Zsye");
330 EXPECT_EQ(EmojiStyle::EMOJI, emoji.getEmojiStyle());
331
332 Locale und = createLocale("und");
333 EXPECT_EQ(EmojiStyle::EMPTY, und.getEmojiStyle());
334 EXPECT_FALSE(emoji == und);
335
336 Locale undExample = createLocale("und-example");
337 EXPECT_EQ(EmojiStyle::EMPTY, undExample.getEmojiStyle());
338 EXPECT_FALSE(emoji == undExample);
339 }
340
TEST(LocaleListTest,subtagEmojiTest)341 TEST(LocaleListTest, subtagEmojiTest) {
342 std::string subtagEmojiStrings[] = {
343 // Duplicate subtag case.
344 "und-Latn-u-em-emoji-u-em-text",
345
346 // Strings that contain language.
347 "und-u-em-emoji", "en-u-em-emoji",
348
349 // Strings that contain the script.
350 "und-Jpan-u-em-emoji", "en-Latn-u-em-emoji", "und-Zsym-u-em-emoji",
351 "und-Zsye-u-em-emoji", "en-Zsym-u-em-emoji", "en-Zsye-u-em-emoji",
352
353 // Strings that contain the country.
354 "und-US-u-em-emoji", "en-US-u-em-emoji", "es-419-u-em-emoji", "und-Latn-US-u-em-emoji",
355 "en-Zsym-US-u-em-emoji", "en-Zsye-US-u-em-emoji", "es-Zsye-419-u-em-emoji",
356
357 // Strings that contain the variant.
358 "de-Latn-DE-1901-u-em-emoji",
359 };
360
361 for (auto subtagEmojiString : subtagEmojiStrings) {
362 SCOPED_TRACE("Test for \"" + subtagEmojiString + "\"");
363 Locale subtagEmoji = createLocale(subtagEmojiString);
364 EXPECT_EQ(EmojiStyle::EMOJI, subtagEmoji.getEmojiStyle());
365 }
366 }
367
TEST(LocaleListTest,subtagTextTest)368 TEST(LocaleListTest, subtagTextTest) {
369 std::string subtagTextStrings[] = {
370 // Duplicate subtag case.
371 "und-Latn-u-em-text-u-em-emoji",
372
373 // Strings that contain language.
374 "und-u-em-text", "en-u-em-text",
375
376 // Strings that contain the script.
377 "und-Latn-u-em-text", "en-Jpan-u-em-text", "und-Zsym-u-em-text", "und-Zsye-u-em-text",
378 "en-Zsym-u-em-text", "en-Zsye-u-em-text",
379
380 // Strings that contain the country.
381 "und-US-u-em-text", "en-US-u-em-text", "es-419-u-em-text", "und-Latn-US-u-em-text",
382 "en-Zsym-US-u-em-text", "en-Zsye-US-u-em-text", "es-Zsye-419-u-em-text",
383
384 // Strings that contain the variant.
385 "de-Latn-DE-1901-u-em-text",
386 };
387
388 for (auto subtagTextString : subtagTextStrings) {
389 SCOPED_TRACE("Test for \"" + subtagTextString + "\"");
390 Locale subtagText = createLocale(subtagTextString);
391 EXPECT_EQ(EmojiStyle::TEXT, subtagText.getEmojiStyle());
392 }
393 }
394
395 // TODO: add more "und" language cases whose language and script are
396 // unexpectedly translated to en-Latn by ICU.
TEST(LocaleListTest,subtagDefaultTest)397 TEST(LocaleListTest, subtagDefaultTest) {
398 std::string subtagDefaultStrings[] = {
399 // Duplicate subtag case.
400 "en-Latn-u-em-default-u-em-emoji", "en-Latn-u-em-default-u-em-text",
401
402 // Strings that contain language.
403 "und-u-em-default", "en-u-em-default",
404
405 // Strings that contain the script.
406 "en-Latn-u-em-default", "en-Zsym-u-em-default", "en-Zsye-u-em-default",
407
408 // Strings that contain the country.
409 "en-US-u-em-default", "en-Latn-US-u-em-default", "es-Latn-419-u-em-default",
410 "en-Zsym-US-u-em-default", "en-Zsye-US-u-em-default", "es-Zsye-419-u-em-default",
411
412 // Strings that contain the variant.
413 "de-Latn-DE-1901-u-em-default",
414 };
415
416 for (auto subtagDefaultString : subtagDefaultStrings) {
417 SCOPED_TRACE("Test for \"" + subtagDefaultString + "\"");
418 Locale subtagDefault = createLocale(subtagDefaultString);
419 EXPECT_EQ(EmojiStyle::DEFAULT, subtagDefault.getEmojiStyle());
420 }
421 }
422
TEST(LocaleListTest,subtagEmptyTest)423 TEST(LocaleListTest, subtagEmptyTest) {
424 std::string subtagEmptyStrings[] = {
425 "und",
426 "jp",
427 "en-US",
428 "en-Latn",
429 "en-Latn-US",
430 "en-Latn-US-u-em",
431 "en-Latn-US-u-em-defaultemoji",
432 "de-Latn-DE-1901",
433 };
434
435 for (auto subtagEmptyString : subtagEmptyStrings) {
436 SCOPED_TRACE("Test for \"" + subtagEmptyString + "\"");
437 Locale subtagEmpty = createLocale(subtagEmptyString);
438 EXPECT_EQ(EmojiStyle::EMPTY, subtagEmpty.getEmojiStyle());
439 }
440 }
441
TEST(LocaleListTest,registerLocaleListTest)442 TEST(LocaleListTest, registerLocaleListTest) {
443 EXPECT_EQ(0UL, registerLocaleList(""));
444 EXPECT_NE(0UL, registerLocaleList("en"));
445 EXPECT_NE(0UL, registerLocaleList("jp"));
446 EXPECT_NE(0UL, registerLocaleList("en,zh-Hans"));
447
448 EXPECT_EQ(registerLocaleList("en"), registerLocaleList("en"));
449 EXPECT_NE(registerLocaleList("en"), registerLocaleList("jp"));
450 EXPECT_NE(registerLocaleList("de"), registerLocaleList("de-1901"));
451
452 EXPECT_EQ(registerLocaleList("en,zh-Hans"), registerLocaleList("en,zh-Hans"));
453 EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("zh-Hans,en"));
454 EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("jp"));
455 EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("en"));
456 EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("en,zh-Hant"));
457 EXPECT_NE(registerLocaleList("de,de-1901"), registerLocaleList("de-1901,de"));
458 }
459
460 // The test font has following glyphs.
461 // U+82A6
462 // U+82A6 U+FE00 (VS1)
463 // U+82A6 U+E0100 (VS17)
464 // U+82A6 U+E0101 (VS18)
465 // U+82A6 U+E0102 (VS19)
466 // U+845B
467 // U+845B U+FE00 (VS2)
468 // U+845B U+E0101 (VS18)
469 // U+845B U+E0102 (VS19)
470 // U+845B U+E0103 (VS20)
471 // U+537F
472 // U+717D U+FE02 (VS3)
473 // U+717D U+E0102 (VS19)
474 // U+717D U+E0103 (VS20)
475 const char kVsTestFont[] = "VariationSelectorTest-Regular.ttf";
476
477 class FontFamilyTest : public testing::Test {
478 public:
SetUp()479 virtual void SetUp() override {
480 if (access(getTestFontPath(kVsTestFont).c_str(), R_OK) != 0) {
481 FAIL() << "Unable to read " << kVsTestFont << ". "
482 << "Please prepare the test data directory. "
483 << "For more details, please see how_to_run.txt.";
484 }
485 }
486 };
487
488 // Asserts that the font family has glyphs for and only for specified codepoint
489 // and variationSelector pairs.
expectVSGlyphs(FontFamily * family,uint32_t codepoint,const std::set<uint32_t> & vs)490 void expectVSGlyphs(FontFamily* family, uint32_t codepoint, const std::set<uint32_t>& vs) {
491 for (uint32_t i = 0xFE00; i <= 0xE01EF; ++i) {
492 // Move to variation selectors supplements after variation selectors.
493 if (i == 0xFF00) {
494 i = 0xE0100;
495 }
496 if (vs.find(i) == vs.end()) {
497 EXPECT_FALSE(family->hasGlyph(codepoint, i))
498 << "Glyph for U+" << std::hex << codepoint << " U+" << i;
499 } else {
500 EXPECT_TRUE(family->hasGlyph(codepoint, i))
501 << "Glyph for U+" << std::hex << codepoint << " U+" << i;
502 }
503 }
504 }
505
TEST_F(FontFamilyTest,hasVariationSelectorTest)506 TEST_F(FontFamilyTest, hasVariationSelectorTest) {
507 std::shared_ptr<FontFamily> family = buildFontFamily(kVsTestFont);
508
509 const uint32_t kVS1 = 0xFE00;
510 const uint32_t kVS2 = 0xFE01;
511 const uint32_t kVS3 = 0xFE02;
512 const uint32_t kVS17 = 0xE0100;
513 const uint32_t kVS18 = 0xE0101;
514 const uint32_t kVS19 = 0xE0102;
515 const uint32_t kVS20 = 0xE0103;
516
517 const uint32_t kSupportedChar1 = 0x82A6;
518 EXPECT_TRUE(family->getCoverage().get(kSupportedChar1));
519 expectVSGlyphs(family.get(), kSupportedChar1, std::set<uint32_t>({kVS1, kVS17, kVS18, kVS19}));
520
521 const uint32_t kSupportedChar2 = 0x845B;
522 EXPECT_TRUE(family->getCoverage().get(kSupportedChar2));
523 expectVSGlyphs(family.get(), kSupportedChar2, std::set<uint32_t>({kVS2, kVS18, kVS19, kVS20}));
524
525 const uint32_t kNoVsSupportedChar = 0x537F;
526 EXPECT_TRUE(family->getCoverage().get(kNoVsSupportedChar));
527 expectVSGlyphs(family.get(), kNoVsSupportedChar, std::set<uint32_t>());
528
529 const uint32_t kVsOnlySupportedChar = 0x717D;
530 EXPECT_FALSE(family->getCoverage().get(kVsOnlySupportedChar));
531 expectVSGlyphs(family.get(), kVsOnlySupportedChar, std::set<uint32_t>({kVS3, kVS19, kVS20}));
532
533 const uint32_t kNotSupportedChar = 0x845C;
534 EXPECT_FALSE(family->getCoverage().get(kNotSupportedChar));
535 expectVSGlyphs(family.get(), kNotSupportedChar, std::set<uint32_t>());
536 }
537
TEST_F(FontFamilyTest,hasVSTableTest)538 TEST_F(FontFamilyTest, hasVSTableTest) {
539 struct TestCase {
540 const std::string fontPath;
541 bool hasVSTable;
542 } testCases[] = {
543 {"Ja.ttf", true}, {"ZhHant.ttf", true}, {"ZhHans.ttf", true},
544 {"Italic.ttf", false}, {"Bold.ttf", false}, {"BoldItalic.ttf", false},
545 };
546
547 for (auto testCase : testCases) {
548 SCOPED_TRACE(testCase.hasVSTable ? "Font " + testCase.fontPath +
549 " should have a variation sequence table."
550 : "Font " + testCase.fontPath +
551 " shouldn't have a variation sequence table.");
552
553 std::shared_ptr<FontFamily> family = buildFontFamily(testCase.fontPath);
554 EXPECT_EQ(testCase.hasVSTable, family->hasVSTable());
555 }
556 }
557
TEST_F(FontFamilyTest,createFamilyWithVariationTest)558 TEST_F(FontFamilyTest, createFamilyWithVariationTest) {
559 // This font has 'wdth' and 'wght' axes.
560 const char kMultiAxisFont[] = "MultiAxis.ttf";
561 const char kNoAxisFont[] = "Regular.ttf";
562
563 std::shared_ptr<FontFamily> multiAxisFamily = buildFontFamily(kMultiAxisFont);
564 std::shared_ptr<FontFamily> noAxisFamily = buildFontFamily(kNoAxisFont);
565
566 {
567 // Do not ceate new instance if none of variations are specified.
568 EXPECT_EQ(nullptr,
569 multiAxisFamily->createFamilyWithVariation(std::vector<FontVariation>()));
570 EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(std::vector<FontVariation>()));
571 }
572 {
573 // New instance should be used for supported variation.
574 std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f}};
575 std::shared_ptr<FontFamily> newFamily(
576 multiAxisFamily->createFamilyWithVariation(variations));
577 EXPECT_NE(nullptr, newFamily.get());
578 EXPECT_NE(multiAxisFamily.get(), newFamily.get());
579 EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(variations));
580 }
581 {
582 // New instance should be used for supported variation. (multiple variations case)
583 std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},
584 {MinikinFont::MakeTag('w', 'g', 'h', 't'), 1.0f}};
585 std::shared_ptr<FontFamily> newFamily(
586 multiAxisFamily->createFamilyWithVariation(variations));
587 EXPECT_NE(nullptr, newFamily.get());
588 EXPECT_NE(multiAxisFamily.get(), newFamily.get());
589 EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(variations));
590 }
591 {
592 // Do not ceate new instance if none of variations are supported.
593 std::vector<FontVariation> variations = {{MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};
594 EXPECT_EQ(nullptr, multiAxisFamily->createFamilyWithVariation(variations));
595 EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(variations));
596 }
597 {
598 // At least one axis is supported, should create new instance.
599 std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},
600 {MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};
601 std::shared_ptr<FontFamily> newFamily(
602 multiAxisFamily->createFamilyWithVariation(variations));
603 EXPECT_NE(nullptr, newFamily.get());
604 EXPECT_NE(multiAxisFamily.get(), newFamily.get());
605 EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(variations));
606 }
607 }
608
TEST_F(FontFamilyTest,coverageTableSelectionTest)609 TEST_F(FontFamilyTest, coverageTableSelectionTest) {
610 // This font supports U+0061. The cmap subtable is format 4 and its platform ID is 0 and
611 // encoding ID is 1.
612 const char kUnicodeEncoding1Font[] = "UnicodeBMPOnly.ttf";
613
614 // This font supports U+0061. The cmap subtable is format 4 and its platform ID is 0 and
615 // encoding ID is 3.
616 const char kUnicodeEncoding3Font[] = "UnicodeBMPOnly2.ttf";
617
618 // This font has both cmap format 4 subtable which platform ID is 0 and encoding ID is 1
619 // and cmap format 14 subtable which platform ID is 0 and encoding ID is 10.
620 // U+0061 is listed in both subtable but U+1F926 is only listed in latter.
621 const char kUnicodeEncoding4Font[] = "UnicodeUCS4.ttf";
622
623 std::shared_ptr<FontFamily> unicodeEnc1Font = buildFontFamily(kUnicodeEncoding1Font);
624 std::shared_ptr<FontFamily> unicodeEnc3Font = buildFontFamily(kUnicodeEncoding3Font);
625 std::shared_ptr<FontFamily> unicodeEnc4Font = buildFontFamily(kUnicodeEncoding4Font);
626
627 EXPECT_TRUE(unicodeEnc1Font->hasGlyph(0x0061, 0));
628 EXPECT_TRUE(unicodeEnc3Font->hasGlyph(0x0061, 0));
629 EXPECT_TRUE(unicodeEnc4Font->hasGlyph(0x0061, 0));
630
631 EXPECT_TRUE(unicodeEnc4Font->hasGlyph(0x1F926, 0));
632 }
633
slantToString(FontStyle::Slant slant)634 const char* slantToString(FontStyle::Slant slant) {
635 if (slant == FontStyle::Slant::ITALIC) {
636 return "ITALIC";
637 } else {
638 return "UPRIGHT";
639 }
640 }
641
fontStyleToString(const FontStyle & style)642 std::string fontStyleToString(const FontStyle& style) {
643 char buf[64] = {};
644 snprintf(buf, sizeof(buf), "FontStyle(weight=%d, slant=%s)", style.weight(),
645 slantToString(style.slant()));
646 return buf;
647 }
648
TEST_F(FontFamilyTest,closestMatch)649 TEST_F(FontFamilyTest, closestMatch) {
650 constexpr char kTestFont[] = "Ascii.ttf";
651
652 constexpr FontStyle::Weight THIN = FontStyle::Weight::THIN;
653 constexpr FontStyle::Weight LIGHT = FontStyle::Weight::LIGHT;
654 constexpr FontStyle::Weight NORMAL = FontStyle::Weight::NORMAL;
655 constexpr FontStyle::Weight MEDIUM = FontStyle::Weight::MEDIUM;
656 constexpr FontStyle::Weight BOLD = FontStyle::Weight::BOLD;
657 constexpr FontStyle::Weight BLACK = FontStyle::Weight::BLACK;
658
659 constexpr FontStyle::Slant UPRIGHT = FontStyle::Slant::UPRIGHT;
660 constexpr FontStyle::Slant ITALIC = FontStyle::Slant::ITALIC;
661
662 const std::vector<FontStyle> STANDARD_SET = {
663 FontStyle(NORMAL, UPRIGHT), // 0
664 FontStyle(BOLD, UPRIGHT), // 1
665 FontStyle(NORMAL, ITALIC), // 2
666 FontStyle(BOLD, ITALIC), // 3
667 };
668
669 const std::vector<FontStyle> FULL_SET = {
670 FontStyle(THIN, UPRIGHT), // 0
671 FontStyle(LIGHT, UPRIGHT), // 1
672 FontStyle(NORMAL, UPRIGHT), // 2
673 FontStyle(MEDIUM, UPRIGHT), // 3
674 FontStyle(BOLD, UPRIGHT), // 4
675 FontStyle(BLACK, UPRIGHT), // 5
676 FontStyle(THIN, ITALIC), // 6
677 FontStyle(LIGHT, ITALIC), // 7
678 FontStyle(NORMAL, ITALIC), // 8
679 FontStyle(MEDIUM, ITALIC), // 9
680 FontStyle(BOLD, ITALIC), // 10
681 FontStyle(BLACK, ITALIC), // 11
682 };
683 struct TestCase {
684 FontStyle wantedStyle;
685 std::vector<FontStyle> familyStyles;
686 size_t expectedIndex;
687 } testCases[] = {
688 {FontStyle(), {FontStyle()}, 0},
689
690 // Exact matches
691 {FontStyle(BOLD), {FontStyle(NORMAL), FontStyle(BOLD)}, 1},
692 {FontStyle(BOLD), {FontStyle(LIGHT), FontStyle(BOLD)}, 1},
693 {FontStyle(LIGHT), {FontStyle(NORMAL), FontStyle(LIGHT)}, 1},
694 {FontStyle(LIGHT), {FontStyle(BOLD), FontStyle(LIGHT)}, 1},
695 {FontStyle(NORMAL), {FontStyle(NORMAL), FontStyle(LIGHT)}, 0},
696 {FontStyle(NORMAL), {FontStyle(NORMAL), FontStyle(BOLD)}, 0},
697 {FontStyle(LIGHT), {FontStyle(LIGHT), FontStyle(NORMAL), FontStyle(BOLD)}, 0},
698 {FontStyle(NORMAL), {FontStyle(LIGHT), FontStyle(NORMAL), FontStyle(BOLD)}, 1},
699 {FontStyle(BOLD), {FontStyle(LIGHT), FontStyle(NORMAL), FontStyle(BOLD)}, 2},
700
701 {FontStyle(UPRIGHT), {FontStyle(UPRIGHT), FontStyle(ITALIC)}, 0},
702 {FontStyle(ITALIC), {FontStyle(UPRIGHT), FontStyle(ITALIC)}, 1},
703
704 {FontStyle(NORMAL, UPRIGHT), STANDARD_SET, 0},
705 {FontStyle(BOLD, UPRIGHT), STANDARD_SET, 1},
706 {FontStyle(NORMAL, ITALIC), STANDARD_SET, 2},
707 {FontStyle(BOLD, ITALIC), STANDARD_SET, 3},
708
709 {FontStyle(NORMAL, UPRIGHT), FULL_SET, 2},
710 {FontStyle(BOLD, UPRIGHT), FULL_SET, 4},
711 {FontStyle(NORMAL, ITALIC), FULL_SET, 8},
712 {FontStyle(BOLD, ITALIC), FULL_SET, 10},
713
714 // TODO: Add fallback expectations. (b/68814338)
715 };
716
717 for (const TestCase& testCase : testCases) {
718 std::vector<std::shared_ptr<MinikinFont>> dummyFonts;
719 std::vector<Font> fonts;
720 for (auto familyStyle : testCase.familyStyles) {
721 std::shared_ptr<MinikinFont> dummyFont(
722 new FreeTypeMinikinFontForTest(getTestFontPath(kTestFont)));
723 dummyFonts.push_back(dummyFont);
724 fonts.push_back(Font::Builder(dummyFont).setStyle(familyStyle).build());
725 }
726
727 FontFamily family(std::move(fonts));
728 FakedFont closest = family.getClosestMatch(testCase.wantedStyle);
729
730 size_t idx = dummyFonts.size();
731 for (size_t i = 0; i < dummyFonts.size(); i++) {
732 if (dummyFonts[i].get() == closest.font->typeface().get()) {
733 idx = i;
734 break;
735 }
736 }
737 ASSERT_NE(idx, dummyFonts.size()) << "The selected font is unknown.";
738 EXPECT_EQ(testCase.expectedIndex, idx)
739 << "Input Style: " << fontStyleToString(testCase.wantedStyle) << std::endl
740 << "Actual Families' Style: " << fontStyleToString(testCase.familyStyles[idx])
741 << std::endl
742 << "Expected Families' Style: "
743 << fontStyleToString(testCase.familyStyles[testCase.expectedIndex]) << std::endl;
744 }
745 }
746
747 } // namespace minikin
748