diff --git a/include/minikin/FontCollection.h b/include/minikin/FontCollection.h index cd14261640cd49a788bf65a0ed0b1dd7816683ed..294692fcede5d75ac4291679f97daa9924b76d18 100644 --- a/include/minikin/FontCollection.h +++ b/include/minikin/FontCollection.h @@ -67,6 +67,16 @@ private: FontFamily* getFamilyForChar(uint32_t ch, uint32_t vs, uint32_t langListId, int variant) const; + uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, int variant, uint32_t langListId, + FontFamily* fontFamily) const; + + uint32_t calcCoverageScore(uint32_t ch, uint32_t vs, FontFamily* fontFamily) const; + + static uint32_t calcLanguageMatchingScore(uint32_t userLangListId, + const FontFamily& fontFamily); + + static uint32_t calcVariantMatchingScore(int variant, const FontFamily& fontFamily); + // static for allocating unique id's static uint32_t sNextId; diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp index 38687001feda6f1efcf168e72da1a5e692a95832..da58fa39c9af0f46bed2f504b26a838f8b62afc0 100644 --- a/libs/minikin/FontCollection.cpp +++ b/libs/minikin/FontCollection.cpp @@ -18,6 +18,7 @@ #define LOG_TAG "Minikin" #include +#include #include "unicode/unistr.h" #include "unicode/unorm2.h" @@ -103,29 +104,135 @@ FontCollection::~FontCollection() { } } +// Special scores for the font fallback. +const uint32_t kUnsupportedFontScore = 0; +const uint32_t kFirstFontScore = UINT32_MAX; + +// Calculates a font score. +// The score of the font family is based on three subscores. +// - Coverage Score: How well the font family covers the given character or variation sequence. +// - Language Score: How well the font family is appropriate for the language. +// - Variant Score: Whether the font family matches the variant. Note that this variant is not the +// one in BCP47. This is our own font variant (e.g., elegant, compact). +// +// Then, there is a priority for these three subscores as follow: +// Coverage Score > Language Score > Variant Score +// The returned score reflects this priority order. +// +// Note that there are two special scores. +// - kUnsupportedFontScore: When the font family doesn't support the variation sequence or even its +// base character. +// - kFirstFontScore: When the font is the first font family in the collection and it supports the +// given character or variation sequence. +uint32_t FontCollection::calcFamilyScore(uint32_t ch, uint32_t vs, int variant, uint32_t langListId, + FontFamily* fontFamily) const { + + const uint32_t coverageScore = calcCoverageScore(ch, vs, fontFamily); + if (coverageScore == kFirstFontScore || coverageScore == kUnsupportedFontScore) { + // No need to calculate other scores. + return coverageScore; + } + + const uint32_t languageScore = calcLanguageMatchingScore(langListId, *fontFamily); + const uint32_t variantScore = calcVariantMatchingScore(variant, *fontFamily); + + // Subscores are encoded into 31 bits representation to meet the subscore priority. + // The highest 2 bits are for coverage score, then following 28 bits are for language score, + // then the last 1 bit is for variant score. + return coverageScore << 29 | languageScore << 1 | variantScore; +} + +// Calculates a font score based on variation sequence coverage. +// - Returns kUnsupportedFontScore if the font doesn't support the variation sequence or its base +// character. +// - Returns kFirstFontScore if the font family is the first font family in the collection and it +// supports the given character or variation sequence. +// - Returns 3 if the font family supports the variation sequence. +// - Returns 2 if the vs is a color variation selector (U+FE0F) and if the font is an emoji font. +// - Returns 2 if the vs is a text variation selector (U+FE0E) and if the font is not an emoji font. +// - Returns 1 if the variation selector is not specified or if the font family only supports the +// variation sequence's base character. +uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs, FontFamily* fontFamily) const { + const bool hasVSGlyph = (vs != 0) && fontFamily->hasVariationSelector(ch, vs); + if (!hasVSGlyph && !fontFamily->getCoverage()->get(ch)) { + // The font doesn't support either variation sequence or even the base character. + return kUnsupportedFontScore; + } + + if ((vs == 0 || hasVSGlyph) && mFamilies[0] == fontFamily) { + // If the first font family supports the given character or variation sequence, always use + // it. + return kFirstFontScore; + } + + if (vs == 0) { + return 1; + } + + if (hasVSGlyph) { + return 3; + } + + if (vs == 0xFE0F || vs == 0xFE0E) { + // TODO use all language in the list. + const FontLanguage lang = FontLanguageListCache::getById(fontFamily->langId())[0]; + const bool hasEmojiFlag = lang.hasEmojiFlag(); + if (vs == 0xFE0F) { + return hasEmojiFlag ? 2 : 1; + } else { // vs == 0xFE0E + return hasEmojiFlag ? 1 : 2; + } + } + return 1; +} + +// Calculates font scores based on the script matching and primary langauge matching. +// +// If the font's script doesn't support the requested script, the font gets a score of 0. If the +// font's script supports the requested script and the font has the same primary language as the +// requested one, the font gets a score of 2. If the font's script supports the requested script +// but the primary language is different from the requested one, the font gets a score of 1. +// +// If two languages in the requested list have the same language score, the font matching with +// higher priority language gets a higher score. For example, in the case the user requested +// language list is "ja-Jpan,en-Latn". The score of for the font of "ja-Jpan" gets a higher score +// than the font of "en-Latn". +// +// To achieve the above two conditions, the language score is determined as follows: +// LanguageScore = s(0) * 3^(m - 1) + s(1) * 3^(m - 2) + ... + s(m - 2) * 3 + s(m - 1) +// Here, m is the maximum number of languages to be compared, and s(i) is the i-th language's +// matching score. The possible values of s(i) are 0, 1 and 2. +uint32_t FontCollection::calcLanguageMatchingScore( + uint32_t userLangListId, const FontFamily& fontFamily) { + const FontLanguages& langList = FontLanguageListCache::getById(userLangListId); + // TODO use all language in the list. + FontLanguage fontLanguage = FontLanguageListCache::getById(fontFamily.langId())[0]; + + const size_t maxCompareNum = std::min(langList.size(), FONT_LANGUAGES_LIMIT); + uint32_t score = fontLanguage.getScoreFor(langList[0]); // maxCompareNum can't be zero. + for (size_t i = 1; i < maxCompareNum; ++i) { + score = score * 3u + fontLanguage.getScoreFor(langList[i]); + } + return score; +} + +// Calculates a font score based on variant ("compact" or "elegant") matching. +// - Returns 1 if the font doesn't have variant or the variant matches with the text style. +// - No score if the font has a variant but it doesn't match with the text style. +uint32_t FontCollection::calcVariantMatchingScore(int variant, const FontFamily& fontFamily) { + return (fontFamily.variant() == 0 || fontFamily.variant() == variant) ? 1 : 0; +} + // Implement heuristic for choosing best-match font. Here are the rules: // 1. If first font in the collection has the character, it wins. -// 2. If a font matches language, it gets a score of 2. -// 3. Matching the "compact" or "elegant" variant adds one to the score. -// 4. If there is a variation selector and a font supports the complete variation sequence, we add -// 8 to the score. -// 5. If there is a color variation selector (U+FE0F), we add 4 to the score if the font is an emoji -// font. This additional score of 4 is only given if the base character is supported in the font, -// but not the whole variation sequence. -// 6. If there is a text variation selector (U+FE0E), we add 4 to the score if the font is not an -// emoji font. This additional score of 4 is only given if the base character is supported in the -// font, but not the whole variation sequence. -// 7. Highest score wins, with ties resolved to the first font. +// 2. Calculate a score for the font family. See comments in calcFamilyScore for the detail. +// 3. Highest score wins, with ties resolved to the first font. FontFamily* FontCollection::getFamilyForChar(uint32_t ch, uint32_t vs, uint32_t langListId, int variant) const { if (ch >= mMaxChar) { return NULL; } - const FontLanguages& langList = FontLanguageListCache::getById(langListId); - // TODO: use all languages in langList. - const FontLanguage lang = (langList.size() == 0) ? FontLanguage() : langList[0]; - // Even if the font supports variation sequence, mRanges isn't aware of the base character of // the sequence. Search all FontFamilies if variation sequence is specified. // TODO: Always use mRanges for font search. @@ -141,40 +248,19 @@ FontFamily* FontCollection::getFamilyForChar(uint32_t ch, uint32_t vs, ALOGD("querying range %zd:%zd\n", range.start, range.end); #endif FontFamily* bestFamily = nullptr; - int bestScore = -1; + uint32_t bestScore = kUnsupportedFontScore; for (size_t i = range.start; i < range.end; i++) { FontFamily* family = familyVec[i]; - const bool hasVSGlyph = (vs != 0) && family->hasVariationSelector(ch, vs); - if (hasVSGlyph || family->getCoverage()->get(ch)) { - if ((vs == 0 || hasVSGlyph) && mFamilies[0] == family) { - // If the first font family in collection supports the given character or sequence, - // always use it. - return family; - } - - // TODO use all language in the list. - FontLanguage fontLang = FontLanguageListCache::getById(family->langId())[0]; - int score = lang.match(fontLang) * 2; - if (family->variant() == 0 || family->variant() == variant) { - score++; - } - if (hasVSGlyph) { - score += 8; - } else if (((vs == 0xFE0F) && fontLang.hasEmojiFlag()) || - ((vs == 0xFE0E) && !fontLang.hasEmojiFlag())) { - score += 4; - } - if (score > bestScore) { - bestScore = score; - bestFamily = family; - } + const uint32_t score = calcFamilyScore(ch, vs, variant, langListId, family); + if (score == kFirstFontScore) { + // If the first font family supports the given character or variation sequence, always + // use it. + return family; + } + if (score > bestScore) { + bestScore = score; + bestFamily = family; } - } - if (bestFamily == nullptr && vs != 0) { - // If no fonts support the codepoint and variation selector pair, - // fallback to select a font family that supports just the base - // character, ignoring the variation selector. - return getFamilyForChar(ch, 0, langListId, variant); } if (bestFamily == nullptr && !mFamilyVec.empty()) { UErrorCode errorCode = U_ZERO_ERROR; diff --git a/libs/minikin/FontLanguage.cpp b/libs/minikin/FontLanguage.cpp index b34040030fbe4c4ee79d42087e975b2a86005b02..8e5c9c481f6a85f98e505507774de603bbc4ef16 100644 --- a/libs/minikin/FontLanguage.cpp +++ b/libs/minikin/FontLanguage.cpp @@ -115,21 +115,29 @@ std::string FontLanguage::getString() const { return std::string(buf, i); } -bool FontLanguage::isEqualScript(const FontLanguage other) const { +bool FontLanguage::isEqualScript(const FontLanguage& other) const { return other.mScript == mScript; } +bool FontLanguage::supportsScript(uint8_t requestedBits) const { + return requestedBits != 0 && (mSubScriptBits & requestedBits) == requestedBits; +} + bool FontLanguage::supportsHbScript(hb_script_t script) const { static_assert(SCRIPT_TAG('J', 'p', 'a', 'n') == HB_TAG('J', 'p', 'a', 'n'), "The Minikin script and HarfBuzz hb_script_t have different encodings."); if (script == mScript) return true; - uint8_t requestedBits = scriptToSubScriptBits(script); - return requestedBits != 0 && (mSubScriptBits & requestedBits) == requestedBits; + return supportsScript(scriptToSubScriptBits(script)); } -int FontLanguage::match(const FontLanguage other) const { - // TODO: Use script for matching. - return *this == other; +int FontLanguage::getScoreFor(const FontLanguage other) const { + if (isUnsupported() || other.isUnsupported()) { + return 0; + } else if (isEqualScript(other) || supportsScript(other.mSubScriptBits)) { + return mLanguage == other.mLanguage ? 2 : 1; + } else { + return 0; + } } #undef SCRIPT_TAG diff --git a/libs/minikin/FontLanguage.h b/libs/minikin/FontLanguage.h index abe7d13179deb0cc05d5cd061299a9678ce4fd67..ee0b505bddeacb0c405b50bbb3831728f7d4edb8 100644 --- a/libs/minikin/FontLanguage.h +++ b/libs/minikin/FontLanguage.h @@ -36,7 +36,7 @@ public: FontLanguage(const char* buf, size_t length); bool operator==(const FontLanguage other) const { - return !isUnsupported() && isEqualScript(other) && isEqualLanguage(other); + return !isUnsupported() && isEqualScript(other) && mLanguage == other.mLanguage; } bool operator!=(const FontLanguage other) const { @@ -46,8 +46,7 @@ public: bool isUnsupported() const { return mLanguage == 0ul; } bool hasEmojiFlag() const { return mSubScriptBits & kEmojiFlag; } - bool isEqualLanguage(const FontLanguage other) const { return mLanguage == other.mLanguage; } - bool isEqualScript(const FontLanguage other) const; + bool isEqualScript(const FontLanguage& other) const; // Returns true if this script supports the given script. For example, ja-Jpan supports Hira, // ja-Hira doesn't support Jpan. @@ -55,8 +54,8 @@ public: std::string getString() const; - // 0 = no match, 1 = language matches - int match(const FontLanguage other) const; + // 0 = no match, 1 = script match, 2 = script and primary language match. + int getScoreFor(const FontLanguage other) const; uint64_t getIdentifier() const { return (uint64_t)mScript << 32 | (uint64_t)mLanguage; } @@ -80,8 +79,11 @@ private: uint8_t mSubScriptBits; static uint8_t scriptToSubScriptBits(uint32_t script); + bool supportsScript(uint8_t requestedBits) const; }; +// Due to the limit of font fallback cost calculation, we can't use anything more than 17 languages. +const size_t FONT_LANGUAGES_LIMIT = 17; typedef std::vector FontLanguages; } // namespace android diff --git a/libs/minikin/FontLanguageListCache.cpp b/libs/minikin/FontLanguageListCache.cpp index 2d64998e7a3846f7b9ecb962636599f583a85364..5d177b5827a98b3a864bfaa90ff7b5887377235e 100644 --- a/libs/minikin/FontLanguageListCache.cpp +++ b/libs/minikin/FontLanguageListCache.cpp @@ -92,15 +92,20 @@ static FontLanguages constructFontLanguages(const std::string& input) { uint64_t identifier = lang.getIdentifier(); if (!lang.isUnsupported() && seen.count(identifier) == 0) { result.push_back(lang); + if (result.size() == FONT_LANGUAGES_LIMIT) { + break; + } seen.insert(identifier); } } - locale.assign(input, currentIdx, input.size() - currentIdx); - size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale); - FontLanguage lang(langTag, length); - uint64_t identifier = lang.getIdentifier(); - if (!lang.isUnsupported() && seen.count(identifier) == 0) { - result.push_back(lang); + if (result.size() < FONT_LANGUAGES_LIMIT) { + locale.assign(input, currentIdx, input.size() - currentIdx); + size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale); + FontLanguage lang(langTag, length); + uint64_t identifier = lang.getIdentifier(); + if (!lang.isUnsupported() && seen.count(identifier) == 0) { + result.push_back(lang); + } } return result; } diff --git a/tests/FontCollectionItemizeTest.cpp b/tests/FontCollectionItemizeTest.cpp index 57409a5e62ce6eba69db4c8a6001d666f39c7e53..446efc6b56563eef6721db7d4205c60cf147f84c 100644 --- a/tests/FontCollectionItemizeTest.cpp +++ b/tests/FontCollectionItemizeTest.cpp @@ -16,18 +16,23 @@ #include +#include "FontLanguageListCache.h" #include "FontLanguage.h" #include "FontTestUtils.h" #include "ICUTestBase.h" #include "MinikinFontForTest.h" #include "MinikinInternal.h" #include "UnicodeUtils.h" +#include "minikin/FontFamily.h" using android::AutoMutex; using android::FontCollection; using android::FontFamily; using android::FontLanguage; +using android::FontLanguages; +using android::FontLanguageListCache; using android::FontStyle; +using android::MinikinFont; using android::gMinikinLock; const char kItemizeFontXml[] = kTestFontDir "itemize.xml"; @@ -68,6 +73,12 @@ const std::string& getFontPath(const FontCollection::Run& run) { return ((MinikinFontForTest*)run.fakedFont.font)->fontPath(); } +// Utility function to obtain FontLanguages from string. +const FontLanguages& registerAndGetFontLanguages(const std::string& lang_string) { + AutoMutex _l(gMinikinLock); + return FontLanguageListCache::getById(FontLanguageListCache::getId(lang_string)); +} + TEST_F(FontCollectionItemizeTest, itemize_latin) { std::unique_ptr collection = getFontCollection(kTestFontDir, kItemizeFontXml); std::vector runs; @@ -451,7 +462,7 @@ TEST_F(FontCollectionItemizeTest, itemize_variationSelector) { EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontPath(runs[0])); // First font family (Regular.ttf) supports U+203C but doesn't support U+203C U+FE0F. - // Emoji.ttf font supports supports U+203C U+FE0F. Emoji.ttf should be selected. + // Emoji.ttf font supports U+203C U+FE0F. Emoji.ttf should be selected. itemize(collection.get(), "U+203C U+FE0F", kZH_HantStyle, &runs); ASSERT_EQ(1U, runs.size()); EXPECT_EQ(0, runs[0].start); @@ -684,6 +695,430 @@ TEST_F(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) { family2->Unref(); } +TEST_F(FontCollectionItemizeTest, itemize_LanguageScore) { + struct TestCase { + std::string userPreferredLanguages; + std::string fontLanguages; + int selectedFontIndex; + } testCases[] = { + // Single user preferred language. + // Exact match case + { "en-Latn", "en-Latn,ja-Jpan", 0 }, + { "ja-Jpan", "en-Latn,ja-Jpan", 1 }, + { "en-Latn", "en-Latn,nl-Latn,es-Latn", 0 }, + { "nl-Latn", "en-Latn,nl-Latn,es-Latn", 1 }, + { "es-Latn", "en-Latn,nl-Latn,es-Latn", 2 }, + { "es-Latn", "en-Latn,en-Latn,nl-Latn", 0 }, + + // Exact script match case + { "en-Latn", "nl-Latn,be-Latn", 0 }, + { "en-Arab", "nl-Latn,ar-Arab", 1 }, + { "en-Latn", "be-Latn,ar-Arab,bd-Beng", 0 }, + { "en-Arab", "be-Latn,ar-Arab,bd-Beng", 1 }, + { "en-Beng", "be-Latn,ar-Arab,bd-Beng", 2 }, + { "en-Beng", "be-Latn,ar-Beng,bd-Beng", 1 }, + { "zh-Hant", "zh-Hant,zh-Hans", 0 }, + { "zh-Hans", "zh-Hant,zh-Hans", 1 }, + + // Subscript match case, e.g. Jpan supports Hira. + { "en-Hira", "ja-Jpan", 0 }, + { "zh-Hani", "zh-Hans,zh-Hant", 0 }, + { "zh-Hani", "zh-Hant,zh-Hans", 0 }, + { "en-Hira", "zh-Hant,ja-Jpan,ja-Jpan", 1 }, + + // Language match case + { "ja-Latn", "zh-Latn,ja-Latn", 1 }, + { "zh-Latn", "zh-Latn,ja-Latn", 0 }, + { "ja-Latn", "zh-Latn,ja-Latn", 1 }, + { "ja-Latn", "zh-Latn,ja-Latn,ja-Latn", 1 }, + + // Mixed case + // Script/subscript match is strongest. + { "ja-Jpan", "en-Latn,ja-Latn,en-Jpan", 2 }, + { "ja-Hira", "en-Latn,ja-Latn,en-Jpan", 2 }, + { "ja-Hira", "en-Latn,ja-Latn,en-Jpan,en-Jpan", 2 }, + + // Language match only happens if the script matches. + { "ja-Hira", "en-Latn,ja-Latn", 0 }, + { "ja-Hira", "en-Jpan,ja-Jpan", 1 }, + + // Multiple languages. + // Even if all fonts have the same score, use the 2nd language for better selection. + { "en-Latn,ja-Jpan", "zh-Hant,zh-Hans,ja-Jpan", 2 }, + { "en-Latn,nl-Latn", "es-Latn,be-Latn,nl-Latn", 2 }, + { "en-Latn,br-Latn,nl-Latn", "es-Latn,be-Latn,nl-Latn", 2 }, + { "en-Latn,br-Latn,nl-Latn", "es-Latn,be-Latn,nl-Latn,nl-Latn", 2 }, + + // Script score. + { "en-Latn,ja-Jpan", "en-Arab,en-Jpan", 1 }, + { "en-Latn,ja-Jpan", "en-Arab,en-Jpan,en-Jpan", 1 }, + + // Language match case + { "en-Latn,ja-Latn", "bd-Latn,ja-Latn", 1 }, + { "en-Latn,ja-Latn", "bd-Latn,ja-Latn,ja-Latn", 1 }, + + // Language match only happens if the script matches. + { "en-Latn,ar-Arab", "en-Beng,ar-Arab", 1 }, + }; + + for (auto testCase : testCases) { + SCOPED_TRACE("Test of user preferred languages: \"" + testCase.userPreferredLanguages + + "\" with font languages: " + testCase.fontLanguages); + + std::vector families; + + // Prepare first font which doesn't supports U+9AA8 + FontFamily* firstFamily = new FontFamily( + FontStyle::registerLanguageList("und"), 0 /* variant */); + MinikinFont* firstFamilyMinikinFont = new MinikinFontForTest(kNoGlyphFont); + firstFamily->addFont(firstFamilyMinikinFont); + families.push_back(firstFamily); + + // Prepare font families + // Each font family is associated with a specified language. All font families except for + // the first font support U+9AA8. + std::unordered_map fontLangIdxMap; + const FontLanguages& fontLanguages = registerAndGetFontLanguages(testCase.fontLanguages); + + for (size_t i = 0; i < fontLanguages.size(); ++i) { + const FontLanguage& fontLanguage = fontLanguages[i]; + FontFamily* family = new FontFamily( + FontStyle::registerLanguageList(fontLanguage.getString()), 0 /* variant */); + MinikinFont* minikin_font = new MinikinFontForTest(kJAFont); + family->addFont(minikin_font); + families.push_back(family); + fontLangIdxMap.insert(std::make_pair(minikin_font, i)); + } + FontCollection collection(families); + for (auto family : families) { + family->Unref(); + } + + // Do itemize + const FontStyle style = FontStyle( + FontStyle::registerLanguageList(testCase.userPreferredLanguages)); + std::vector runs; + itemize(&collection, "U+9AA8", style, &runs); + ASSERT_EQ(1U, runs.size()); + ASSERT_NE(nullptr, runs[0].fakedFont.font); + + // First family doesn't support U+9AA8 and others support it, so the first font should not + // be selected. + EXPECT_NE(firstFamilyMinikinFont, runs[0].fakedFont.font); + + // Lookup used font family by MinikinFont*. + const int usedLangIndex = fontLangIdxMap[runs[0].fakedFont.font]; + EXPECT_EQ(testCase.selectedFontIndex, usedLangIndex); + } +} + +TEST_F(FontCollectionItemizeTest, itemize_LanguageAndCoverage) { + struct TestCase { + std::string testString; + std::string requestedLanguages; + std::string expectedFont; + } testCases[] = { + // Following test cases verify that following rules in font fallback chain. + // - If the first font in the collection supports the given character or variation sequence, + // it should be selected. + // - If the font doesn't support the given character, variation sequence or its base + // character, it should not be selected. + // - If two or more fonts match the requested languages, the font matches with the highest + // priority language should be selected. + // - If two or more fonts get the same score, the font listed earlier in the XML file + // (here, kItemizeFontXml) should be selected. + + // Regardless of language, the first font is always selected if it covers the code point. + { "'a'", "", kLatinFont}, + { "'a'", "en-Latn", kLatinFont}, + { "'a'", "ja-Jpan", kLatinFont}, + { "'a'", "ja-Jpan,en-Latn", kLatinFont}, + { "'a'", "zh-Hans,zh-Hant,en-Latn,ja-Jpan,fr-Latn", kLatinFont}, + + // U+81ED is supported by both the ja font and zh-Hans font. + { "U+81ED", "", kZH_HansFont }, // zh-Hans font is listed before ja font. + { "U+81ED", "en-Latn", kZH_HansFont }, // zh-Hans font is listed before ja font. + { "U+81ED", "ja-Jpan", kJAFont }, + { "U+81ED", "zh-Hans", kZH_HansFont }, + + { "U+81ED", "ja-Jpan,en-Latn", kJAFont }, + { "U+81ED", "en-Latn,ja-Jpan", kJAFont }, + { "U+81ED", "en-Latn,zh-Hans", kZH_HansFont }, + { "U+81ED", "zh-Hans,en-Latn", kZH_HansFont }, + { "U+81ED", "ja-Jpan,zh-Hans", kJAFont }, + { "U+81ED", "zh-Hans,ja-Jpan", kZH_HansFont }, + + { "U+81ED", "en-Latn,zh-Hans,ja-Jpan", kZH_HansFont }, + { "U+81ED", "en-Latn,ja-Jpan,zh-Hans", kJAFont }, + { "U+81ED", "en-Latn,zh-Hans,ja-Jpan", kZH_HansFont }, + { "U+81ED", "ja-Jpan,en-Latn,zh-Hans", kJAFont }, + { "U+81ED", "ja-Jpan,zh-Hans,en-Latn", kJAFont }, + { "U+81ED", "zh-Hans,en-Latn,ja-Jpan", kZH_HansFont }, + { "U+81ED", "zh-Hans,ja-Jpan,en-Latn", kZH_HansFont }, + + // U+304A is only supported by ja font. + { "U+304A", "", kJAFont }, + { "U+304A", "ja-Jpan", kJAFont }, + { "U+304A", "zh-Hant", kJAFont }, + { "U+304A", "zh-Hans", kJAFont }, + + { "U+304A", "ja-Jpan,zh-Hant", kJAFont }, + { "U+304A", "zh-Hant,ja-Jpan", kJAFont }, + { "U+304A", "zh-Hans,zh-Hant", kJAFont }, + { "U+304A", "zh-Hant,zh-Hans", kJAFont }, + { "U+304A", "zh-Hans,ja-Jpan", kJAFont }, + { "U+304A", "ja-Jpan,zh-Hans", kJAFont }, + + { "U+304A", "zh-Hans,ja-Jpan,zh-Hant", kJAFont }, + { "U+304A", "zh-Hans,zh-Hant,ja-Jpan", kJAFont }, + { "U+304A", "ja-Jpan,zh-Hans,zh-Hant", kJAFont }, + { "U+304A", "ja-Jpan,zh-Hant,zh-Hans", kJAFont }, + { "U+304A", "zh-Hant,zh-Hans,ja-Jpan", kJAFont }, + { "U+304A", "zh-Hant,ja-Jpan,zh-Hans", kJAFont }, + + // U+242EE is supported by both ja font and zh-Hant fonts but not by zh-Hans font. + { "U+242EE", "", kJAFont }, // ja font is listed before zh-Hant font. + { "U+242EE", "ja-Jpan", kJAFont }, + { "U+242EE", "zh-Hans", kJAFont }, + { "U+242EE", "zh-Hant", kZH_HantFont }, + + { "U+242EE", "ja-Jpan,zh-Hant", kJAFont }, + { "U+242EE", "zh-Hant,ja-Jpan", kZH_HantFont }, + { "U+242EE", "zh-Hans,zh-Hant", kZH_HantFont }, + { "U+242EE", "zh-Hant,zh-Hans", kZH_HantFont }, + { "U+242EE", "zh-Hans,ja-Jpan", kJAFont }, + { "U+242EE", "ja-Jpan,zh-Hans", kJAFont }, + + { "U+242EE", "zh-Hans,ja-Jpan,zh-Hant", kJAFont }, + { "U+242EE", "zh-Hans,zh-Hant,ja-Jpan", kZH_HantFont }, + { "U+242EE", "ja-Jpan,zh-Hans,zh-Hant", kJAFont }, + { "U+242EE", "ja-Jpan,zh-Hant,zh-Hans", kJAFont }, + { "U+242EE", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont }, + { "U+242EE", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont }, + + // U+9AA8 is supported by all ja-Jpan, zh-Hans, zh-Hant fonts. + { "U+9AA8", "", kZH_HansFont }, // zh-Hans font is listed before ja and zh-Hant fonts. + { "U+9AA8", "ja-Jpan", kJAFont }, + { "U+9AA8", "zh-Hans", kZH_HansFont }, + { "U+9AA8", "zh-Hant", kZH_HantFont }, + + { "U+9AA8", "ja-Jpan,zh-Hant", kJAFont }, + { "U+9AA8", "zh-Hant,ja-Jpan", kZH_HantFont }, + { "U+9AA8", "zh-Hans,zh-Hant", kZH_HansFont }, + { "U+9AA8", "zh-Hant,zh-Hans", kZH_HantFont }, + { "U+9AA8", "zh-Hans,ja-Jpan", kZH_HansFont }, + { "U+9AA8", "ja-Jpan,zh-Hans", kJAFont }, + + { "U+9AA8", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont }, + { "U+9AA8", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont }, + { "U+9AA8", "ja-Jpan,zh-Hans,zh-Hant", kJAFont }, + { "U+9AA8", "ja-Jpan,zh-Hant,zh-Hans", kJAFont }, + { "U+9AA8", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont }, + { "U+9AA8", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont }, + + // U+242EE U+FE00 is supported by ja font but not by zh-Hans or zh-Hant fonts. + { "U+242EE U+FE00", "", kJAFont }, + { "U+242EE U+FE00", "ja-Jpan", kJAFont }, + { "U+242EE U+FE00", "zh-Hant", kJAFont }, + { "U+242EE U+FE00", "zh-Hans", kJAFont }, + + { "U+242EE U+FE00", "ja-Jpan,zh-Hant", kJAFont }, + { "U+242EE U+FE00", "zh-Hant,ja-Jpan", kJAFont }, + { "U+242EE U+FE00", "zh-Hans,zh-Hant", kJAFont }, + { "U+242EE U+FE00", "zh-Hant,zh-Hans", kJAFont }, + { "U+242EE U+FE00", "zh-Hans,ja-Jpan", kJAFont }, + { "U+242EE U+FE00", "ja-Jpan,zh-Hans", kJAFont }, + + { "U+242EE U+FE00", "zh-Hans,ja-Jpan,zh-Hant", kJAFont }, + { "U+242EE U+FE00", "zh-Hans,zh-Hant,ja-Jpan", kJAFont }, + { "U+242EE U+FE00", "ja-Jpan,zh-Hans,zh-Hant", kJAFont }, + { "U+242EE U+FE00", "ja-Jpan,zh-Hant,zh-Hans", kJAFont }, + { "U+242EE U+FE00", "zh-Hant,zh-Hans,ja-Jpan", kJAFont }, + { "U+242EE U+FE00", "zh-Hant,ja-Jpan,zh-Hans", kJAFont }, + + // U+3402 U+E0100 is supported by both zh-Hans and zh-Hant but not by ja font. + { "U+3402 U+E0100", "", kZH_HansFont }, // zh-Hans font is listed before zh-Hant font. + { "U+3402 U+E0100", "ja-Jpan", kZH_HansFont }, // zh-Hans font is listed before zh-Hant font. + { "U+3402 U+E0100", "zh-Hant", kZH_HantFont }, + { "U+3402 U+E0100", "zh-Hans", kZH_HansFont }, + + { "U+3402 U+E0100", "ja-Jpan,zh-Hant", kZH_HantFont }, + { "U+3402 U+E0100", "zh-Hant,ja-Jpan", kZH_HantFont }, + { "U+3402 U+E0100", "zh-Hans,zh-Hant", kZH_HansFont }, + { "U+3402 U+E0100", "zh-Hant,zh-Hans", kZH_HantFont }, + { "U+3402 U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont }, + { "U+3402 U+E0100", "ja-Jpan,zh-Hans", kZH_HansFont }, + + { "U+3402 U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont }, + { "U+3402 U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont }, + { "U+3402 U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kZH_HansFont }, + { "U+3402 U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kZH_HantFont }, + { "U+3402 U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont }, + { "U+3402 U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont }, + + // No font supports U+4444 U+FE00 but only zh-Hans supports its base character U+4444. + { "U+4444 U+FE00", "", kZH_HansFont }, + { "U+4444 U+FE00", "ja-Jpan", kZH_HansFont }, + { "U+4444 U+FE00", "zh-Hant", kZH_HansFont }, + { "U+4444 U+FE00", "zh-Hans", kZH_HansFont }, + + { "U+4444 U+FE00", "ja-Jpan,zh-Hant", kZH_HansFont }, + { "U+4444 U+FE00", "zh-Hant,ja-Jpan", kZH_HansFont }, + { "U+4444 U+FE00", "zh-Hans,zh-Hant", kZH_HansFont }, + { "U+4444 U+FE00", "zh-Hant,zh-Hans", kZH_HansFont }, + { "U+4444 U+FE00", "zh-Hans,ja-Jpan", kZH_HansFont }, + { "U+4444 U+FE00", "ja-Jpan,zh-Hans", kZH_HansFont }, + + { "U+4444 U+FE00", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont }, + { "U+4444 U+FE00", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont }, + { "U+4444 U+FE00", "ja-Jpan,zh-Hans,zh-Hant", kZH_HansFont }, + { "U+4444 U+FE00", "ja-Jpan,zh-Hant,zh-Hans", kZH_HansFont }, + { "U+4444 U+FE00", "zh-Hant,zh-Hans,ja-Jpan", kZH_HansFont }, + { "U+4444 U+FE00", "zh-Hant,ja-Jpan,zh-Hans", kZH_HansFont }, + + // No font supports U+81ED U+E0100 but ja and zh-Hans support its base character U+81ED. + // zh-Hans font is listed before ja font. + { "U+81ED U+E0100", "", kZH_HansFont }, + { "U+81ED U+E0100", "ja-Jpan", kJAFont }, + { "U+81ED U+E0100", "zh-Hant", kZH_HansFont }, + { "U+81ED U+E0100", "zh-Hans", kZH_HansFont }, + + { "U+81ED U+E0100", "ja-Jpan,zh-Hant", kJAFont }, + { "U+81ED U+E0100", "zh-Hant,ja-Jpan", kJAFont }, + { "U+81ED U+E0100", "zh-Hans,zh-Hant", kZH_HansFont }, + { "U+81ED U+E0100", "zh-Hant,zh-Hans", kZH_HansFont }, + { "U+81ED U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont }, + { "U+81ED U+E0100", "ja-Jpan,zh-Hans", kJAFont }, + + { "U+81ED U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont }, + { "U+81ED U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont }, + { "U+81ED U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kJAFont }, + { "U+81ED U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kJAFont }, + { "U+81ED U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HansFont }, + { "U+81ED U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kJAFont }, + + // No font supports U+9AA8 U+E0100 but all zh-Hans zh-hant ja fonts support its base + // character U+9AA8. + // zh-Hans font is listed before ja and zh-Hant fonts. + { "U+9AA8 U+E0100", "", kZH_HansFont }, + { "U+9AA8 U+E0100", "ja-Jpan", kJAFont }, + { "U+9AA8 U+E0100", "zh-Hans", kZH_HansFont }, + { "U+9AA8 U+E0100", "zh-Hant", kZH_HantFont }, + + { "U+9AA8 U+E0100", "ja-Jpan,zh-Hant", kJAFont }, + { "U+9AA8 U+E0100", "zh-Hant,ja-Jpan", kZH_HantFont }, + { "U+9AA8 U+E0100", "zh-Hans,zh-Hant", kZH_HansFont }, + { "U+9AA8 U+E0100", "zh-Hant,zh-Hans", kZH_HantFont }, + { "U+9AA8 U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont }, + { "U+9AA8 U+E0100", "ja-Jpan,zh-Hans", kJAFont }, + + { "U+9AA8 U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont }, + { "U+9AA8 U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont }, + { "U+9AA8 U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kJAFont }, + { "U+9AA8 U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kJAFont }, + { "U+9AA8 U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont }, + { "U+9AA8 U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont }, + + // All zh-Hans,zh-Hant,ja fonts support U+35A8 U+E0100 and its base character U+35A8. + // zh-Hans font is listed before ja and zh-Hant fonts. + { "U+35A8", "", kZH_HansFont }, + { "U+35A8", "ja-Jpan", kJAFont }, + { "U+35A8", "zh-Hans", kZH_HansFont }, + { "U+35A8", "zh-Hant", kZH_HantFont }, + + { "U+35A8", "ja-Jpan,zh-Hant", kJAFont }, + { "U+35A8", "zh-Hant,ja-Jpan", kZH_HantFont }, + { "U+35A8", "zh-Hans,zh-Hant", kZH_HansFont }, + { "U+35A8", "zh-Hant,zh-Hans", kZH_HantFont }, + { "U+35A8", "zh-Hans,ja-Jpan", kZH_HansFont }, + { "U+35A8", "ja-Jpan,zh-Hans", kJAFont }, + + { "U+35A8", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont }, + { "U+35A8", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont }, + { "U+35A8", "ja-Jpan,zh-Hans,zh-Hant", kJAFont }, + { "U+35A8", "ja-Jpan,zh-Hant,zh-Hans", kJAFont }, + { "U+35A8", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont }, + { "U+35A8", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont }, + + // All zh-Hans,zh-Hant,ja fonts support U+35B6 U+E0100, but zh-Hant and ja fonts support its + // base character U+35B6. + // ja font is listed before zh-Hant font. + { "U+35B6", "", kJAFont }, + { "U+35B6", "ja-Jpan", kJAFont }, + { "U+35B6", "zh-Hant", kZH_HantFont }, + { "U+35B6", "zh-Hans", kJAFont }, + + { "U+35B6", "ja-Jpan,zh-Hant", kJAFont }, + { "U+35B6", "zh-Hant,ja-Jpan", kZH_HantFont }, + { "U+35B6", "zh-Hans,zh-Hant", kZH_HantFont }, + { "U+35B6", "zh-Hant,zh-Hans", kZH_HantFont }, + { "U+35B6", "zh-Hans,ja-Jpan", kJAFont }, + { "U+35B6", "ja-Jpan,zh-Hans", kJAFont }, + + { "U+35B6", "zh-Hans,ja-Jpan,zh-Hant", kJAFont }, + { "U+35B6", "zh-Hans,zh-Hant,ja-Jpan", kZH_HantFont }, + { "U+35B6", "ja-Jpan,zh-Hans,zh-Hant", kJAFont }, + { "U+35B6", "ja-Jpan,zh-Hant,zh-Hans", kJAFont }, + { "U+35B6", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont }, + { "U+35B6", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont }, + + // All zh-Hans,zh-Hant,ja fonts support U+35C5 U+E0100, but only ja font supports its base + // character U+35C5. + { "U+35C5", "", kJAFont }, + { "U+35C5", "ja-Jpan", kJAFont }, + { "U+35C5", "zh-Hant", kJAFont }, + { "U+35C5", "zh-Hans", kJAFont }, + + { "U+35C5", "ja-Jpan,zh-Hant", kJAFont }, + { "U+35C5", "zh-Hant,ja-Jpan", kJAFont }, + { "U+35C5", "zh-Hans,zh-Hant", kJAFont }, + { "U+35C5", "zh-Hant,zh-Hans", kJAFont }, + { "U+35C5", "zh-Hans,ja-Jpan", kJAFont }, + { "U+35C5", "ja-Jpan,zh-Hans", kJAFont }, + + { "U+35C5", "zh-Hans,ja-Jpan,zh-Hant", kJAFont }, + { "U+35C5", "zh-Hans,zh-Hant,ja-Jpan", kJAFont }, + { "U+35C5", "ja-Jpan,zh-Hans,zh-Hant", kJAFont }, + { "U+35C5", "ja-Jpan,zh-Hant,zh-Hans", kJAFont }, + { "U+35C5", "zh-Hant,zh-Hans,ja-Jpan", kJAFont }, + { "U+35C5", "zh-Hant,ja-Jpan,zh-Hans", kJAFont }, + + // None of ja-Jpan, zh-Hant, zh-Hans font supports U+1F469. Emoji font supports it. + { "U+1F469", "", kEmojiFont }, + { "U+1F469", "ja-Jpan", kEmojiFont }, + { "U+1F469", "zh-Hant", kEmojiFont }, + { "U+1F469", "zh-Hans", kEmojiFont }, + + { "U+1F469", "ja-Jpan,zh-Hant", kEmojiFont }, + { "U+1F469", "zh-Hant,ja-Jpan", kEmojiFont }, + { "U+1F469", "zh-Hans,zh-Hant", kEmojiFont }, + { "U+1F469", "zh-Hant,zh-Hans", kEmojiFont }, + { "U+1F469", "zh-Hans,ja-Jpan", kEmojiFont }, + { "U+1F469", "ja-Jpan,zh-Hans", kEmojiFont }, + + { "U+1F469", "zh-Hans,ja-Jpan,zh-Hant", kEmojiFont }, + { "U+1F469", "zh-Hans,zh-Hant,ja-Jpan", kEmojiFont }, + { "U+1F469", "ja-Jpan,zh-Hans,zh-Hant", kEmojiFont }, + { "U+1F469", "ja-Jpan,zh-Hant,zh-Hans", kEmojiFont }, + { "U+1F469", "zh-Hant,zh-Hans,ja-Jpan", kEmojiFont }, + { "U+1F469", "zh-Hant,ja-Jpan,zh-Hans", kEmojiFont }, + }; + + std::unique_ptr collection = getFontCollection(kTestFontDir, kItemizeFontXml); + + for (auto testCase : testCases) { + SCOPED_TRACE("Test for \"" + testCase.testString + "\" with languages " + + testCase.requestedLanguages); + + std::vector runs; + const FontStyle style = + FontStyle(FontStyle::registerLanguageList(testCase.requestedLanguages)); + itemize(collection.get(), testCase.testString.c_str(), style, &runs); + ASSERT_EQ(1U, runs.size()); + EXPECT_EQ(testCase.expectedFont, getFontPath(runs[0])); + } +} + TEST_F(FontCollectionItemizeTest, itemize_emojiSelection) { std::unique_ptr collection = getFontCollection(kTestFontDir, kEmojiXmlFile); std::vector runs; diff --git a/tests/MinikinFontForTest.h b/tests/MinikinFontForTest.h index 5738666f77daf92e69e63a41778514d691d25acf..790348deaea460443640868817f568583b88f76d 100644 --- a/tests/MinikinFontForTest.h +++ b/tests/MinikinFontForTest.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef MINIKIN_TEST_MINIKIN_FONT_FOR_TEST_H +#define MINIKIN_TEST_MINIKIN_FONT_FOR_TEST_H + #include class SkTypeface; @@ -35,3 +38,5 @@ private: SkTypeface *mTypeface; const std::string mFontPath; }; + +#endif // MINIKIN_TEST_MINIKIN_FONT_FOR_TEST_H diff --git a/tests/data/Ja.ttf b/tests/data/Ja.ttf index ba2405fa27dd6d7aafb1df71eebacf120da6485a..b3d4e848ecf02d71b1cdf7ddc2cc4e649a7ae1cd 100644 Binary files a/tests/data/Ja.ttf and b/tests/data/Ja.ttf differ diff --git a/tests/data/Ja.ttx b/tests/data/Ja.ttx index eeef0c02aad708245a59320312470e803e766417..562ff04ca5bee7effafdcfb146b02a4475c952a4 100644 --- a/tests/data/Ja.ttx +++ b/tests/data/Ja.ttx @@ -30,6 +30,10 @@ + + + + @@ -159,6 +163,10 @@ + + + + @@ -176,6 +184,10 @@ + + + + @@ -183,6 +195,9 @@ + + + @@ -235,6 +250,18 @@ + + + + + + + + + + + + diff --git a/tests/data/ZhHans.ttf b/tests/data/ZhHans.ttf index 81e1179ff6c03cbbcbf0b3c86cddb5b1e48f522a..08adc1b300f8013b64519eeeaf1645afaead87b9 100644 Binary files a/tests/data/ZhHans.ttf and b/tests/data/ZhHans.ttf differ diff --git a/tests/data/ZhHans.ttx b/tests/data/ZhHans.ttx index 261e4027b5389072f055d8b6c44c85aeb2cc9369..238bd5e6d006ef23fe3a9991a7d319dfa5046221 100644 --- a/tests/data/ZhHans.ttx +++ b/tests/data/ZhHans.ttx @@ -26,6 +26,11 @@ + + + + + @@ -151,6 +156,11 @@ + + + + + @@ -164,7 +174,16 @@ + + + + + + + + + @@ -204,6 +223,21 @@ + + + + + + + + + + + + + + + diff --git a/tests/data/ZhHant.ttf b/tests/data/ZhHant.ttf index ad040e2bc166d4bb9d4e9bce30d122b7aad1e05c..592117d5a782f98cfd9eff5ef390eb4907d07d1f 100644 Binary files a/tests/data/ZhHant.ttf and b/tests/data/ZhHant.ttf differ diff --git a/tests/data/ZhHant.ttx b/tests/data/ZhHant.ttx index bda3527a03436d42acfb4d8a36d4e9447c6eba92..31fada959779a2a3d7eeb1ef95bb38cf9ad8889b 100644 --- a/tests/data/ZhHant.ttx +++ b/tests/data/ZhHant.ttx @@ -19,6 +19,11 @@ + + + + + @@ -137,13 +142,28 @@ + + + + + + + + + + + + + + + @@ -161,6 +181,21 @@ + + + + + + + + + + + + + + +