Support multiple locales for font language settings.
Some fonts support multiple scripts, for example, some fonts for Korean supports not only "Kore" but also "Jamo". To select fonts based on their multiple languages, this CL introduces the following changes: - Compares all languages of the font family and use the maximum score for font selection. - Even if each language of the font family doesn't support the requested language, the font get score of 2 if the requested font is covered by all of the languages of the font family. For example, the font for "ko-Hang,ko-Hani" gets score of 2 for the requested language "ko-Kore". Bug: 26687969 Change-Id: I7f13b51464c9b01982bb573251d77052b9ddbd70
This commit is contained in:
@@ -177,9 +177,15 @@ uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs, FontFamily*
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vs == 0xFE0F || vs == 0xFE0E) {
|
if (vs == 0xFE0F || vs == 0xFE0E) {
|
||||||
// TODO use all language in the list.
|
const FontLanguages& langs = FontLanguageListCache::getById(fontFamily->langId());
|
||||||
const FontLanguage lang = FontLanguageListCache::getById(fontFamily->langId())[0];
|
bool hasEmojiFlag = false;
|
||||||
const bool hasEmojiFlag = lang.hasEmojiFlag();
|
for (size_t i = 0; i < langs.size(); ++i) {
|
||||||
|
if (langs[i].hasEmojiFlag()) {
|
||||||
|
hasEmojiFlag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (vs == 0xFE0F) {
|
if (vs == 0xFE0F) {
|
||||||
return hasEmojiFlag ? 2 : 1;
|
return hasEmojiFlag ? 2 : 1;
|
||||||
} else { // vs == 0xFE0E
|
} else { // vs == 0xFE0E
|
||||||
@@ -208,13 +214,12 @@ uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs, FontFamily*
|
|||||||
uint32_t FontCollection::calcLanguageMatchingScore(
|
uint32_t FontCollection::calcLanguageMatchingScore(
|
||||||
uint32_t userLangListId, const FontFamily& fontFamily) {
|
uint32_t userLangListId, const FontFamily& fontFamily) {
|
||||||
const FontLanguages& langList = FontLanguageListCache::getById(userLangListId);
|
const FontLanguages& langList = FontLanguageListCache::getById(userLangListId);
|
||||||
// TODO use all language in the list.
|
const FontLanguages& fontLanguages = FontLanguageListCache::getById(fontFamily.langId());
|
||||||
FontLanguage fontLanguage = FontLanguageListCache::getById(fontFamily.langId())[0];
|
|
||||||
|
|
||||||
const size_t maxCompareNum = std::min(langList.size(), FONT_LANGUAGES_LIMIT);
|
const size_t maxCompareNum = std::min(langList.size(), FONT_LANGUAGES_LIMIT);
|
||||||
uint32_t score = fontLanguage.getScoreFor(langList[0]); // maxCompareNum can't be zero.
|
uint32_t score = 0;
|
||||||
for (size_t i = 1; i < maxCompareNum; ++i) {
|
for (size_t i = 0; i < maxCompareNum; ++i) {
|
||||||
score = score * 3u + fontLanguage.getScoreFor(langList[i]);
|
score = score * 3u + langList[i].calcScoreFor(fontLanguages);
|
||||||
}
|
}
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,24 +126,59 @@ bool FontLanguage::isEqualScript(const FontLanguage& other) const {
|
|||||||
return other.mScript == mScript;
|
return other.mScript == mScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FontLanguage::supportsScript(uint8_t requestedBits) const {
|
// static
|
||||||
return requestedBits != 0 && (mSubScriptBits & requestedBits) == requestedBits;
|
bool FontLanguage::supportsScript(uint8_t providedBits, uint8_t requestedBits) {
|
||||||
|
return requestedBits != 0 && (providedBits & requestedBits) == requestedBits;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FontLanguage::supportsHbScript(hb_script_t script) const {
|
bool FontLanguage::supportsHbScript(hb_script_t script) const {
|
||||||
static_assert(SCRIPT_TAG('J', 'p', 'a', 'n') == HB_TAG('J', 'p', 'a', 'n'),
|
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.");
|
"The Minikin script and HarfBuzz hb_script_t have different encodings.");
|
||||||
if (script == mScript) return true;
|
if (script == mScript) return true;
|
||||||
return supportsScript(scriptToSubScriptBits(script));
|
return supportsScript(mSubScriptBits, scriptToSubScriptBits(script));
|
||||||
}
|
}
|
||||||
|
|
||||||
int FontLanguage::getScoreFor(const FontLanguage other) const {
|
int FontLanguage::calcScoreFor(const FontLanguages& supported) const {
|
||||||
if (isUnsupported() || other.isUnsupported()) {
|
int score = 0;
|
||||||
return 0;
|
for (size_t i = 0; i < supported.size(); ++i) {
|
||||||
} else if (isEqualScript(other) || supportsScript(other.mSubScriptBits)) {
|
if (isEqualScript(supported[i]) ||
|
||||||
return mLanguage == other.mLanguage ? 2 : 1;
|
supportsScript(supported[i].mSubScriptBits, mSubScriptBits)) {
|
||||||
} else {
|
if (mLanguage == supported[i].mLanguage) {
|
||||||
return 0;
|
return 2;
|
||||||
|
} else {
|
||||||
|
score = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score == 1) {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportsScript(supported.getUnionOfSubScriptBits(), mSubScriptBits)) {
|
||||||
|
// Gives score of 2 only if the language matches all of the font languages except for the
|
||||||
|
// exact match case handled above.
|
||||||
|
return (mLanguage == supported[0].mLanguage && supported.isAllTheSameLanguage()) ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
FontLanguages::FontLanguages(std::vector<FontLanguage>&& languages)
|
||||||
|
: mLanguages(std::move(languages)) {
|
||||||
|
if (mLanguages.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FontLanguage& lang = mLanguages[0];
|
||||||
|
|
||||||
|
mIsAllTheSameLanguage = true;
|
||||||
|
mUnionOfSubScriptBits = lang.mSubScriptBits;
|
||||||
|
for (size_t i = 1; i < mLanguages.size(); ++i) {
|
||||||
|
mUnionOfSubScriptBits |= mLanguages[i].mSubScriptBits;
|
||||||
|
if (mIsAllTheSameLanguage && lang.mLanguage != mLanguages[i].mLanguage) {
|
||||||
|
mIsAllTheSameLanguage = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,11 @@
|
|||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
|
// Due to the limits in font fallback score calculation, we can't use anything more than 17
|
||||||
|
// languages.
|
||||||
|
const size_t FONT_LANGUAGES_LIMIT = 17;
|
||||||
|
class FontLanguages;
|
||||||
|
|
||||||
// FontLanguage is a compact representation of a BCP 47 language tag. It
|
// FontLanguage is a compact representation of a BCP 47 language tag. It
|
||||||
// does not capture all possible information, only what directly affects
|
// does not capture all possible information, only what directly affects
|
||||||
// font rendering.
|
// font rendering.
|
||||||
@@ -54,12 +59,16 @@ public:
|
|||||||
|
|
||||||
std::string getString() const;
|
std::string getString() const;
|
||||||
|
|
||||||
|
// Calculates a matching score. This score represents how well the input languages cover this
|
||||||
|
// language. The maximum score in the language list is returned.
|
||||||
// 0 = no match, 1 = script match, 2 = script and primary language match.
|
// 0 = no match, 1 = script match, 2 = script and primary language match.
|
||||||
int getScoreFor(const FontLanguage other) const;
|
int calcScoreFor(const FontLanguages& supported) const;
|
||||||
|
|
||||||
uint64_t getIdentifier() const { return (uint64_t)mScript << 32 | (uint64_t)mLanguage; }
|
uint64_t getIdentifier() const { return (uint64_t)mScript << 32 | (uint64_t)mLanguage; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class FontLanguages; // for FontLanguages constructor
|
||||||
|
|
||||||
// ISO 15924 compliant script code. The 4 chars script code are packed into a 32 bit integer.
|
// ISO 15924 compliant script code. The 4 chars script code are packed into a 32 bit integer.
|
||||||
uint32_t mScript;
|
uint32_t mScript;
|
||||||
|
|
||||||
@@ -80,12 +89,37 @@ private:
|
|||||||
uint8_t mSubScriptBits;
|
uint8_t mSubScriptBits;
|
||||||
|
|
||||||
static uint8_t scriptToSubScriptBits(uint32_t script);
|
static uint8_t scriptToSubScriptBits(uint32_t script);
|
||||||
bool supportsScript(uint8_t requestedBits) const;
|
|
||||||
|
// Returns true if the provide subscript bits has the requested subscript bits.
|
||||||
|
// Note that this function returns false if the requested subscript bits are empty.
|
||||||
|
static bool supportsScript(uint8_t providedBits, uint8_t requestedBits);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Due to the limit of font fallback cost calculation, we can't use anything more than 17 languages.
|
// An immutable list of languages.
|
||||||
const size_t FONT_LANGUAGES_LIMIT = 17;
|
class FontLanguages {
|
||||||
typedef std::vector<FontLanguage> FontLanguages;
|
public:
|
||||||
|
FontLanguages(std::vector<FontLanguage>&& languages);
|
||||||
|
FontLanguages() : mUnionOfSubScriptBits(0), mIsAllTheSameLanguage(false) {}
|
||||||
|
FontLanguages(FontLanguages&&) = default;
|
||||||
|
|
||||||
|
size_t size() const { return mLanguages.size(); }
|
||||||
|
bool empty() const { return mLanguages.empty(); }
|
||||||
|
const FontLanguage& operator[] (size_t n) const { return mLanguages[n]; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct FontLanguage; // for calcScoreFor
|
||||||
|
|
||||||
|
std::vector<FontLanguage> mLanguages;
|
||||||
|
uint8_t mUnionOfSubScriptBits;
|
||||||
|
bool mIsAllTheSameLanguage;
|
||||||
|
|
||||||
|
uint8_t getUnionOfSubScriptBits() const { return mUnionOfSubScriptBits; }
|
||||||
|
bool isAllTheSameLanguage() const { return mIsAllTheSameLanguage; }
|
||||||
|
|
||||||
|
// Do not copy and assign.
|
||||||
|
FontLanguages(const FontLanguages&) = delete;
|
||||||
|
void operator=(const FontLanguages&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ static size_t toLanguageTag(char* output, size_t outSize, const std::string& loc
|
|||||||
return outLength;
|
return outLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FontLanguages constructFontLanguages(const std::string& input) {
|
static std::vector<FontLanguage> parseLanguageList(const std::string& input) {
|
||||||
FontLanguages result;
|
std::vector<FontLanguage> result;
|
||||||
size_t currentIdx = 0;
|
size_t currentIdx = 0;
|
||||||
size_t commaLoc = 0;
|
size_t commaLoc = 0;
|
||||||
char langTag[ULOC_FULLNAME_CAPACITY];
|
char langTag[ULOC_FULLNAME_CAPACITY];
|
||||||
@@ -121,11 +121,11 @@ uint32_t FontLanguageListCache::getId(const std::string& languages) {
|
|||||||
|
|
||||||
// Given language list is not in cache. Insert it and return newly assigned ID.
|
// Given language list is not in cache. Insert it and return newly assigned ID.
|
||||||
const uint32_t nextId = inst->mLanguageLists.size();
|
const uint32_t nextId = inst->mLanguageLists.size();
|
||||||
FontLanguages fontLanguages = constructFontLanguages(languages);
|
FontLanguages fontLanguages(parseLanguageList(languages));
|
||||||
if (fontLanguages.empty()) {
|
if (fontLanguages.empty()) {
|
||||||
return kEmptyListId;
|
return kEmptyListId;
|
||||||
}
|
}
|
||||||
inst->mLanguageLists.push_back(fontLanguages);
|
inst->mLanguageLists.push_back(std::move(fontLanguages));
|
||||||
inst->mLanguageListLookupTable.insert(std::make_pair(languages, nextId));
|
inst->mLanguageListLookupTable.insert(std::make_pair(languages, nextId));
|
||||||
return nextId;
|
return nextId;
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ FontLanguageListCache* FontLanguageListCache::getInstance() {
|
|||||||
|
|
||||||
// Insert an empty language list for mapping default language list to kEmptyListId.
|
// Insert an empty language list for mapping default language list to kEmptyListId.
|
||||||
// The default language list has only one FontLanguage and it is the unsupported language.
|
// The default language list has only one FontLanguage and it is the unsupported language.
|
||||||
instance->mLanguageLists.push_back(FontLanguages({FontLanguage()}));
|
instance->mLanguageLists.push_back(FontLanguages());
|
||||||
instance->mLanguageListLookupTable.insert(std::make_pair("", kEmptyListId));
|
instance->mLanguageListLookupTable.insert(std::make_pair("", kEmptyListId));
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
|
|||||||
@@ -698,72 +698,104 @@ TEST_F(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) {
|
|||||||
TEST_F(FontCollectionItemizeTest, itemize_LanguageScore) {
|
TEST_F(FontCollectionItemizeTest, itemize_LanguageScore) {
|
||||||
struct TestCase {
|
struct TestCase {
|
||||||
std::string userPreferredLanguages;
|
std::string userPreferredLanguages;
|
||||||
std::string fontLanguages;
|
std::vector<std::string> fontLanguages;
|
||||||
int selectedFontIndex;
|
int selectedFontIndex;
|
||||||
} testCases[] = {
|
} testCases[] = {
|
||||||
|
// Font can specify empty language.
|
||||||
|
{ "und", { "", "" }, 0 },
|
||||||
|
{ "und", { "", "en-Latn" }, 0 },
|
||||||
|
{ "en-Latn", { "", "" }, 0 },
|
||||||
|
{ "en-Latn", { "", "en-Latn" }, 1 },
|
||||||
|
|
||||||
// Single user preferred language.
|
// Single user preferred language.
|
||||||
// Exact match case
|
// Exact match case
|
||||||
{ "en-Latn", "en-Latn,ja-Jpan", 0 },
|
{ "en-Latn", { "en-Latn", "ja-Jpan" }, 0 },
|
||||||
{ "ja-Jpan", "en-Latn,ja-Jpan", 1 },
|
{ "ja-Jpan", { "en-Latn", "ja-Jpan" }, 1 },
|
||||||
{ "en-Latn", "en-Latn,nl-Latn,es-Latn", 0 },
|
{ "en-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 0 },
|
||||||
{ "nl-Latn", "en-Latn,nl-Latn,es-Latn", 1 },
|
{ "nl-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 1 },
|
||||||
{ "es-Latn", "en-Latn,nl-Latn,es-Latn", 2 },
|
{ "es-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 2 },
|
||||||
{ "es-Latn", "en-Latn,en-Latn,nl-Latn", 0 },
|
{ "es-Latn", { "en-Latn", "en-Latn", "nl-Latn" }, 0 },
|
||||||
|
|
||||||
// Exact script match case
|
// Exact script match case
|
||||||
{ "en-Latn", "nl-Latn,be-Latn", 0 },
|
{ "en-Latn", { "nl-Latn", "e-Latn" }, 0 },
|
||||||
{ "en-Arab", "nl-Latn,ar-Arab", 1 },
|
{ "en-Arab", { "nl-Latn", "ar-Arab" }, 1 },
|
||||||
{ "en-Latn", "be-Latn,ar-Arab,bd-Beng", 0 },
|
{ "en-Latn", { "be-Latn", "ar-Arab", "d-Beng" }, 0 },
|
||||||
{ "en-Arab", "be-Latn,ar-Arab,bd-Beng", 1 },
|
{ "en-Arab", { "be-Latn", "ar-Arab", "d-Beng" }, 1 },
|
||||||
{ "en-Beng", "be-Latn,ar-Arab,bd-Beng", 2 },
|
{ "en-Beng", { "be-Latn", "ar-Arab", "d-Beng" }, 2 },
|
||||||
{ "en-Beng", "be-Latn,ar-Beng,bd-Beng", 1 },
|
{ "en-Beng", { "be-Latn", "ar-Beng", "d-Beng" }, 1 },
|
||||||
{ "zh-Hant", "zh-Hant,zh-Hans", 0 },
|
{ "zh-Hant", { "zh-Hant", "zh-Hans" }, 0 },
|
||||||
{ "zh-Hans", "zh-Hant,zh-Hans", 1 },
|
{ "zh-Hans", { "zh-Hant", "zh-Hans" }, 1 },
|
||||||
|
|
||||||
// Subscript match case, e.g. Jpan supports Hira.
|
// Subscript match case, e.g. Jpan supports Hira.
|
||||||
{ "en-Hira", "ja-Jpan", 0 },
|
{ "en-Hira", { "ja-Jpan" }, 0 },
|
||||||
{ "zh-Hani", "zh-Hans,zh-Hant", 0 },
|
{ "zh-Hani", { "zh-Hans", "zh-Hant" }, 0 },
|
||||||
{ "zh-Hani", "zh-Hant,zh-Hans", 0 },
|
{ "zh-Hani", { "zh-Hant", "zh-Hans" }, 0 },
|
||||||
{ "en-Hira", "zh-Hant,ja-Jpan,ja-Jpan", 1 },
|
{ "en-Hira", { "zh-Hant", "ja-Jpan", "ja-Jpan" }, 1 },
|
||||||
|
|
||||||
// Language match case
|
// Language match case
|
||||||
{ "ja-Latn", "zh-Latn,ja-Latn", 1 },
|
{ "ja-Latn", { "zh-Latn", "ja-Latn" }, 1 },
|
||||||
{ "zh-Latn", "zh-Latn,ja-Latn", 0 },
|
{ "zh-Latn", { "zh-Latn", "ja-Latn" }, 0 },
|
||||||
{ "ja-Latn", "zh-Latn,ja-Latn", 1 },
|
{ "ja-Latn", { "zh-Latn", "ja-Latn" }, 1 },
|
||||||
{ "ja-Latn", "zh-Latn,ja-Latn,ja-Latn", 1 },
|
{ "ja-Latn", { "zh-Latn", "ja-Latn", "ja-Latn" }, 1 },
|
||||||
|
|
||||||
// Mixed case
|
// Mixed case
|
||||||
// Script/subscript match is strongest.
|
// Script/subscript match is strongest.
|
||||||
{ "ja-Jpan", "en-Latn,ja-Latn,en-Jpan", 2 },
|
{ "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" }, 2 },
|
||||||
{ "ja-Hira", "en-Latn,ja-Latn,en-Jpan,en-Jpan", 2 },
|
{ "ja-Hira", { "en-Latn", "ja-Latn", "en-Jpan", "en-Jpan" }, 2 },
|
||||||
|
|
||||||
// Language match only happens if the script matches.
|
// Language match only happens if the script matches.
|
||||||
{ "ja-Hira", "en-Latn,ja-Latn", 0 },
|
{ "ja-Hira", { "en-Latn", "ja-Latn" }, 0 },
|
||||||
{ "ja-Hira", "en-Jpan,ja-Jpan", 1 },
|
{ "ja-Hira", { "en-Jpan", "ja-Jpan" }, 1 },
|
||||||
|
|
||||||
// Multiple languages.
|
// Multiple languages.
|
||||||
// Even if all fonts have the same score, use the 2nd language for better selection.
|
// 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,ja-Jpan", { "zh-Hant", "zh-Hans", "ja-Jpan" }, 2 },
|
||||||
{ "en-Latn,nl-Latn", "es-Latn,be-Latn,nl-Latn", 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" }, 2 },
|
||||||
{ "en-Latn,br-Latn,nl-Latn", "es-Latn,be-Latn,nl-Latn,nl-Latn", 2 },
|
{ "en-Latn,br-Latn,nl-Latn", { "es-Latn", "be-Latn", "nl-Latn", "nl-Latn" }, 2 },
|
||||||
|
|
||||||
// Script score.
|
// Script score.
|
||||||
{ "en-Latn,ja-Jpan", "en-Arab,en-Jpan", 1 },
|
{ "en-Latn,ja-Jpan", { "en-Arab", "en-Jpan" }, 1 },
|
||||||
{ "en-Latn,ja-Jpan", "en-Arab,en-Jpan,en-Jpan", 1 },
|
{ "en-Latn,ja-Jpan", { "en-Arab", "en-Jpan", "en-Jpan" }, 1 },
|
||||||
|
|
||||||
// Language match case
|
// Language match case
|
||||||
{ "en-Latn,ja-Latn", "bd-Latn,ja-Latn", 1 },
|
{ "en-Latn,ja-Latn", { "bd-Latn", "ja-Latn" }, 1 },
|
||||||
{ "en-Latn,ja-Latn", "bd-Latn,ja-Latn,ja-Latn", 1 },
|
{ "en-Latn,ja-Latn", { "bd-Latn", "ja-Latn", "ja-Latn" }, 1 },
|
||||||
|
|
||||||
// Language match only happens if the script matches.
|
// Language match only happens if the script matches.
|
||||||
{ "en-Latn,ar-Arab", "en-Beng,ar-Arab", 1 },
|
{ "en-Latn,ar-Arab", { "en-Beng", "ar-Arab" }, 1 },
|
||||||
|
|
||||||
|
// Multiple languages in the font settings.
|
||||||
|
{ "ko-Jamo", { "ja-Jpan", "ko-Kore", "ko-Kore,ko-Jamo"}, 2 },
|
||||||
|
{ "en-Latn", { "ja-Jpan", "en-Latn,ja-Jpan"}, 1 },
|
||||||
|
{ "en-Latn", { "ja-Jpan", "ja-Jpan,en-Latn"}, 1 },
|
||||||
|
{ "en-Latn", { "ja-Jpan,zh-Hant", "en-Latn,ja-Jpan", "en-Latn"}, 1 },
|
||||||
|
{ "en-Latn", { "zh-Hant,ja-Jpan", "ja-Jpan,en-Latn", "en-Latn"}, 1 },
|
||||||
|
|
||||||
|
// Kore = Hang + Hani, etc.
|
||||||
|
{ "ko-Kore", { "ko-Hang", "ko-Jamo,ko-Hani", "ko-Hang,ko-Hani"}, 2 },
|
||||||
|
{ "ja-Hrkt", { "ja-Hira", "ja-Kana", "ja-Hira,ja-Kana"}, 2 },
|
||||||
|
{ "ja-Jpan", { "ja-Hira", "ja-Kana", "ja-Hani", "ja-Hira,ja-Kana,ja-Hani"}, 3 },
|
||||||
|
{ "zh-Hanb", { "zh-Hant", "zh-Bopo", "zh-Hant,zh-Bopo"}, 2 },
|
||||||
|
{ "zh-Hanb", { "ja-Hanb", "zh-Hant,zh-Bopo"}, 1 },
|
||||||
|
|
||||||
|
// Language match with unified subscript bits.
|
||||||
|
{ "zh-Hanb", { "zh-Hant", "zh-Bopo", "ja-Hant,ja-Bopo", "zh-Hant,zh-Bopo"}, 3 },
|
||||||
|
{ "zh-Hanb", { "zh-Hant", "zh-Bopo", "ja-Hant,zh-Bopo", "zh-Hant,zh-Bopo"}, 3 },
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto testCase : testCases) {
|
for (auto testCase : testCases) {
|
||||||
|
std::string fontLanguagesStr = "{";
|
||||||
|
for (size_t i = 0; i < testCase.fontLanguages.size(); ++i) {
|
||||||
|
if (i != 0) {
|
||||||
|
fontLanguagesStr += ", ";
|
||||||
|
}
|
||||||
|
fontLanguagesStr += "\"" + testCase.fontLanguages[i] + "\"";
|
||||||
|
}
|
||||||
|
fontLanguagesStr += "}";
|
||||||
SCOPED_TRACE("Test of user preferred languages: \"" + testCase.userPreferredLanguages +
|
SCOPED_TRACE("Test of user preferred languages: \"" + testCase.userPreferredLanguages +
|
||||||
"\" with font languages: " + testCase.fontLanguages);
|
"\" with font languages: " + fontLanguagesStr);
|
||||||
|
|
||||||
std::vector<FontFamily*> families;
|
std::vector<FontFamily*> families;
|
||||||
|
|
||||||
@@ -778,12 +810,10 @@ TEST_F(FontCollectionItemizeTest, itemize_LanguageScore) {
|
|||||||
// Each font family is associated with a specified language. All font families except for
|
// Each font family is associated with a specified language. All font families except for
|
||||||
// the first font support U+9AA8.
|
// the first font support U+9AA8.
|
||||||
std::unordered_map<MinikinFont*, int> fontLangIdxMap;
|
std::unordered_map<MinikinFont*, int> fontLangIdxMap;
|
||||||
const FontLanguages& fontLanguages = registerAndGetFontLanguages(testCase.fontLanguages);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < fontLanguages.size(); ++i) {
|
for (size_t i = 0; i < testCase.fontLanguages.size(); ++i) {
|
||||||
const FontLanguage& fontLanguage = fontLanguages[i];
|
|
||||||
FontFamily* family = new FontFamily(
|
FontFamily* family = new FontFamily(
|
||||||
FontStyle::registerLanguageList(fontLanguage.getString()), 0 /* variant */);
|
FontStyle::registerLanguageList(testCase.fontLanguages[i]), 0 /* variant */);
|
||||||
MinikinFont* minikin_font = new MinikinFontForTest(kJAFont);
|
MinikinFont* minikin_font = new MinikinFontForTest(kJAFont);
|
||||||
family->addFont(minikin_font);
|
family->addFont(minikin_font);
|
||||||
families.push_back(family);
|
families.push_back(family);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace android {
|
|||||||
typedef ICUTestBase FontLanguagesTest;
|
typedef ICUTestBase FontLanguagesTest;
|
||||||
typedef ICUTestBase FontLanguageTest;
|
typedef ICUTestBase FontLanguageTest;
|
||||||
|
|
||||||
static FontLanguages createFontLanguages(const std::string& input) {
|
static const FontLanguages& createFontLanguages(const std::string& input) {
|
||||||
AutoMutex _l(gMinikinLock);
|
AutoMutex _l(gMinikinLock);
|
||||||
uint32_t langId = FontLanguageListCache::getId(input);
|
uint32_t langId = FontLanguageListCache::getId(input);
|
||||||
return FontLanguageListCache::getById(langId);
|
return FontLanguageListCache::getById(langId);
|
||||||
@@ -217,35 +217,30 @@ TEST_F(FontLanguagesTest, basicTests) {
|
|||||||
EXPECT_EQ(0u, emptyLangs.size());
|
EXPECT_EQ(0u, emptyLangs.size());
|
||||||
|
|
||||||
FontLanguage english = createFontLanguage("en");
|
FontLanguage english = createFontLanguage("en");
|
||||||
FontLanguages singletonLangs = createFontLanguages("en");
|
const FontLanguages& singletonLangs = createFontLanguages("en");
|
||||||
EXPECT_EQ(1u, singletonLangs.size());
|
EXPECT_EQ(1u, singletonLangs.size());
|
||||||
EXPECT_EQ(english, singletonLangs[0]);
|
EXPECT_EQ(english, singletonLangs[0]);
|
||||||
|
|
||||||
FontLanguage french = createFontLanguage("fr");
|
FontLanguage french = createFontLanguage("fr");
|
||||||
FontLanguages twoLangs = createFontLanguages("en,fr");
|
const FontLanguages& twoLangs = createFontLanguages("en,fr");
|
||||||
EXPECT_EQ(2u, twoLangs.size());
|
EXPECT_EQ(2u, twoLangs.size());
|
||||||
EXPECT_EQ(english, twoLangs[0]);
|
EXPECT_EQ(english, twoLangs[0]);
|
||||||
EXPECT_EQ(french, twoLangs[1]);
|
EXPECT_EQ(french, twoLangs[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(FontLanguagesTest, unsupportedLanguageTests) {
|
TEST_F(FontLanguagesTest, unsupportedLanguageTests) {
|
||||||
FontLanguage unsupportedLang = createFontLanguage("abcd");
|
const FontLanguages& oneUnsupported = createFontLanguages("abcd-example");
|
||||||
ASSERT_TRUE(unsupportedLang.isUnsupported());
|
EXPECT_TRUE(oneUnsupported.empty());
|
||||||
|
|
||||||
FontLanguages oneUnsupported = createFontLanguages("abcd-example");
|
const FontLanguages& twoUnsupporteds = createFontLanguages("abcd-example,abcd-example");
|
||||||
EXPECT_EQ(1u, oneUnsupported.size());
|
EXPECT_TRUE(twoUnsupporteds.empty());
|
||||||
EXPECT_TRUE(oneUnsupported[0].isUnsupported());
|
|
||||||
|
|
||||||
FontLanguages twoUnsupporteds = createFontLanguages("abcd-example,abcd-example");
|
|
||||||
EXPECT_EQ(1u, twoUnsupporteds.size());
|
|
||||||
EXPECT_TRUE(twoUnsupporteds[0].isUnsupported());
|
|
||||||
|
|
||||||
FontLanguage english = createFontLanguage("en");
|
FontLanguage english = createFontLanguage("en");
|
||||||
FontLanguages firstUnsupported = createFontLanguages("abcd-example,en");
|
const FontLanguages& firstUnsupported = createFontLanguages("abcd-example,en");
|
||||||
EXPECT_EQ(1u, firstUnsupported.size());
|
EXPECT_EQ(1u, firstUnsupported.size());
|
||||||
EXPECT_EQ(english, firstUnsupported[0]);
|
EXPECT_EQ(english, firstUnsupported[0]);
|
||||||
|
|
||||||
FontLanguages lastUnsupported = createFontLanguages("en,abcd-example");
|
const FontLanguages& lastUnsupported = createFontLanguages("en,abcd-example");
|
||||||
EXPECT_EQ(1u, lastUnsupported.size());
|
EXPECT_EQ(1u, lastUnsupported.size());
|
||||||
EXPECT_EQ(english, lastUnsupported[0]);
|
EXPECT_EQ(english, lastUnsupported[0]);
|
||||||
}
|
}
|
||||||
@@ -256,20 +251,20 @@ TEST_F(FontLanguagesTest, repeatedLanguageTests) {
|
|||||||
FontLanguage englishInLatn = createFontLanguage("en-Latn");
|
FontLanguage englishInLatn = createFontLanguage("en-Latn");
|
||||||
ASSERT_TRUE(english == englishInLatn);
|
ASSERT_TRUE(english == englishInLatn);
|
||||||
|
|
||||||
FontLanguages langs = createFontLanguages("en,en-Latn");
|
const FontLanguages& langs = createFontLanguages("en,en-Latn");
|
||||||
EXPECT_EQ(1u, langs.size());
|
EXPECT_EQ(1u, langs.size());
|
||||||
EXPECT_EQ(english, langs[0]);
|
EXPECT_EQ(english, langs[0]);
|
||||||
|
|
||||||
// Country codes are ignored.
|
// Country codes are ignored.
|
||||||
FontLanguages fr = createFontLanguages("fr,fr-CA,fr-FR");
|
const FontLanguages& fr = createFontLanguages("fr,fr-CA,fr-FR");
|
||||||
EXPECT_EQ(1u, fr.size());
|
EXPECT_EQ(1u, fr.size());
|
||||||
EXPECT_EQ(french, fr[0]);
|
EXPECT_EQ(french, fr[0]);
|
||||||
|
|
||||||
// The order should be kept.
|
// The order should be kept.
|
||||||
langs = createFontLanguages("en,fr,en-Latn");
|
const FontLanguages& langs2 = createFontLanguages("en,fr,en-Latn");
|
||||||
EXPECT_EQ(2u, langs.size());
|
EXPECT_EQ(2u, langs2.size());
|
||||||
EXPECT_EQ(english, langs[0]);
|
EXPECT_EQ(english, langs2[0]);
|
||||||
EXPECT_EQ(french, langs[1]);
|
EXPECT_EQ(french, langs2[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(FontLanguagesTest, undEmojiTests) {
|
TEST_F(FontLanguagesTest, undEmojiTests) {
|
||||||
|
|||||||
@@ -56,18 +56,18 @@ TEST_F(FontLanguageListCacheTest, getById) {
|
|||||||
FontLanguage english = FontLanguageListCache::getById(enLangId)[0];
|
FontLanguage english = FontLanguageListCache::getById(enLangId)[0];
|
||||||
FontLanguage japanese = FontLanguageListCache::getById(jpLangId)[0];
|
FontLanguage japanese = FontLanguageListCache::getById(jpLangId)[0];
|
||||||
|
|
||||||
FontLanguages defLangs = FontLanguageListCache::getById(0);
|
const FontLanguages& defLangs = FontLanguageListCache::getById(0);
|
||||||
EXPECT_EQ(1UL, defLangs.size());
|
EXPECT_TRUE(defLangs.empty());
|
||||||
EXPECT_TRUE(defLangs[0].isUnsupported());
|
|
||||||
|
|
||||||
FontLanguages langs = FontLanguageListCache::getById(FontLanguageListCache::getId("en"));
|
const FontLanguages& langs = FontLanguageListCache::getById(FontLanguageListCache::getId("en"));
|
||||||
ASSERT_EQ(1UL, langs.size());
|
ASSERT_EQ(1UL, langs.size());
|
||||||
EXPECT_EQ(english, langs[0]);
|
EXPECT_EQ(english, langs[0]);
|
||||||
|
|
||||||
langs = FontLanguageListCache::getById(FontLanguageListCache::getId("en,jp"));
|
const FontLanguages& langs2 =
|
||||||
ASSERT_EQ(2UL, langs.size());
|
FontLanguageListCache::getById(FontLanguageListCache::getId("en,jp"));
|
||||||
EXPECT_EQ(english, langs[0]);
|
ASSERT_EQ(2UL, langs2.size());
|
||||||
EXPECT_EQ(japanese, langs[1]);
|
EXPECT_EQ(english, langs2[0]);
|
||||||
|
EXPECT_EQ(japanese, langs2[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // android
|
} // android
|
||||||
|
|||||||
Reference in New Issue
Block a user