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