From bb5c10092c3ec3246a9f4c52cd6b620e86fa5bd8 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Mon, 14 Dec 2015 18:33:23 -0800 Subject: [PATCH] Save all kind of script tags into FontLanguage. The main purpose of this CL is expanding FontLanguage to be able to save full script tag. Previously, FontLangauge kept only limited script tags. With this CL, FontLanguage keeps all script tags. This CL contains the following changes: - FontLanguage changes: -- Moved to private directory not to be instantiated outside of Minikin. -- Removed bool(), bits(), FontLanguage(uint32_t) methods which are no longer used. -- Change the FontLanguage internal data structure. -- Introduces script match logic. - FontLanguages changes: -- Moved to private directory not to be instantiated outside of Minikin. -- This is now std::vector - FontLanguageListCache changes: -- Now FontLanguageListCache::getId through FontStyle::registerLanguageList is the only way to instantiate the FontLanguage. -- Normalize input to be BCP47 compliant identifier by ICU. Bug: 26168983 Change-Id: I8df992a6851021903478972601a9a5c9424b100c --- .../src/flutter/include/minikin/FontFamily.h | 64 +---- engine/src/flutter/libs/minikin/Android.mk | 1 + .../flutter/libs/minikin/FontCollection.cpp | 10 +- .../src/flutter/libs/minikin/FontFamily.cpp | 120 +-------- .../src/flutter/libs/minikin/FontLanguage.cpp | 135 ++++++++++ .../src/flutter/libs/minikin/FontLanguage.h | 89 +++++++ .../libs/minikin/FontLanguageListCache.cpp | 90 ++++++- .../libs/minikin/FontLanguageListCache.h | 1 + engine/src/flutter/libs/minikin/Layout.cpp | 13 +- .../tests/FontCollectionItemizeTest.cpp | 32 +-- engine/src/flutter/tests/FontFamilyTest.cpp | 232 ++++++++++++++++-- .../tests/FontLanguageListCacheTest.cpp | 18 +- engine/src/flutter/tests/FontTestUtils.cpp | 7 +- engine/src/flutter/tests/ICUTestBase.h | 52 ++++ 14 files changed, 639 insertions(+), 225 deletions(-) create mode 100644 engine/src/flutter/libs/minikin/FontLanguage.cpp create mode 100644 engine/src/flutter/libs/minikin/FontLanguage.h create mode 100644 engine/src/flutter/tests/ICUTestBase.h diff --git a/engine/src/flutter/include/minikin/FontFamily.h b/engine/src/flutter/include/minikin/FontFamily.h index 00130e6f5d..aa2e0ac2e4 100644 --- a/engine/src/flutter/include/minikin/FontFamily.h +++ b/engine/src/flutter/include/minikin/FontFamily.h @@ -30,62 +30,6 @@ namespace android { class MinikinFont; -// FontLanguage is a compact representation of a bcp-47 language tag. It -// does not capture all possible information, only what directly affects -// font rendering. -class FontLanguage { - friend class FontStyle; - friend class FontLanguages; -public: - FontLanguage() : mBits(0) { } - - // Parse from string - FontLanguage(const char* buf, size_t size); - - bool operator==(const FontLanguage other) const { - return mBits != kUnsupportedLanguage && mBits == other.mBits; - } - operator bool() const { return mBits != 0; } - - bool isUnsupported() const { return mBits == kUnsupportedLanguage; } - bool hasEmojiFlag() const { return isUnsupported() ? false : (mBits & kEmojiFlag); } - - std::string getString() const; - - // 0 = no match, 1 = language matches - int match(const FontLanguage other) const; - -private: - explicit FontLanguage(uint32_t bits) : mBits(bits) { } - - uint32_t bits() const { return mBits; } - - static const uint32_t kUnsupportedLanguage = 0xFFFFFFFFu; - static const uint32_t kBaseLangMask = 0xFFFFFFu; - static const uint32_t kHansFlag = 1u << 24; - static const uint32_t kHantFlag = 1u << 25; - static const uint32_t kEmojiFlag = 1u << 26; - static const uint32_t kScriptMask = kHansFlag | kHantFlag | kEmojiFlag; - uint32_t mBits; -}; - -// A list of zero or more instances of FontLanguage, in the order of -// preference. Used for further resolution of rendering results. -class FontLanguages { -public: - FontLanguages() { mLangs.clear(); } - - // Parse from string, which is a comma-separated list of languages - FontLanguages(const char* buf, size_t size); - - const FontLanguage& operator[](size_t index) const { return mLangs.at(index); } - - size_t size() const { return mLangs.size(); } - -private: - std::vector mLangs; -}; - // FontStyle represents all style information needed to select an actual font // from a collection. The implementation is packed into two 32-bit words // so it can be efficiently copied, embedded in other objects, etc. @@ -158,7 +102,9 @@ class FontFamily : public MinikinRefCounted { public: FontFamily() : mHbFont(nullptr) { } - FontFamily(FontLanguage lang, int variant) : mLang(lang), mVariant(variant), mHbFont(nullptr) { + FontFamily(int variant); + + FontFamily(uint32_t langId, int variant) : mLangId(langId), mVariant(variant), mHbFont(nullptr) { } ~FontFamily(); @@ -169,7 +115,7 @@ public: void addFont(MinikinFont* typeface, FontStyle style); FakedFont getClosestMatch(FontStyle style) const; - FontLanguage lang() const { return mLang; } + uint32_t langId() const { return mLangId; } int variant() const { return mVariant; } // API's for enumerating the fonts in a family. These don't guarantee any particular order @@ -200,7 +146,7 @@ private: MinikinFont* typeface; FontStyle style; }; - FontLanguage mLang; + uint32_t mLangId; int mVariant; std::vector mFonts; diff --git a/engine/src/flutter/libs/minikin/Android.mk b/engine/src/flutter/libs/minikin/Android.mk index 4c945a60c9..42fdca3919 100644 --- a/engine/src/flutter/libs/minikin/Android.mk +++ b/engine/src/flutter/libs/minikin/Android.mk @@ -21,6 +21,7 @@ minikin_src_files := \ CmapCoverage.cpp \ FontCollection.cpp \ FontFamily.cpp \ + FontLanguage.cpp \ FontLanguageListCache.cpp \ GraphemeBreak.cpp \ HbFaceCache.cpp \ diff --git a/engine/src/flutter/libs/minikin/FontCollection.cpp b/engine/src/flutter/libs/minikin/FontCollection.cpp index bba2ef9535..62c70310c0 100644 --- a/engine/src/flutter/libs/minikin/FontCollection.cpp +++ b/engine/src/flutter/libs/minikin/FontCollection.cpp @@ -22,6 +22,7 @@ #include "unicode/unistr.h" #include "unicode/unorm2.h" +#include "FontLanguage.h" #include "FontLanguageListCache.h" #include "MinikinInternal.h" #include @@ -150,14 +151,17 @@ FontFamily* FontCollection::getFamilyForChar(uint32_t ch, uint32_t vs, // always use it. return family; } - int score = lang.match(family->lang()) * 2; + + // 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) && family->lang().hasEmojiFlag()) || - ((vs == 0xFE0E) && !family->lang().hasEmojiFlag())) { + } else if (((vs == 0xFE0F) && fontLang.hasEmojiFlag()) || + ((vs == 0xFE0E) && !fontLang.hasEmojiFlag())) { score += 4; } if (score > bestScore) { diff --git a/engine/src/flutter/libs/minikin/FontFamily.cpp b/engine/src/flutter/libs/minikin/FontFamily.cpp index 7639831240..1405789028 100644 --- a/engine/src/flutter/libs/minikin/FontFamily.cpp +++ b/engine/src/flutter/libs/minikin/FontFamily.cpp @@ -16,8 +16,6 @@ #define LOG_TAG "Minikin" -#include - #include #include #include @@ -28,6 +26,7 @@ #include +#include "FontLanguage.h" #include "FontLanguageListCache.h" #include "HbFaceCache.h" #include "MinikinInternal.h" @@ -41,117 +40,6 @@ using std::vector; namespace android { -// Parse bcp-47 language identifier into internal structure -FontLanguage::FontLanguage(const char* buf, size_t size) { - uint32_t bits = 0; - size_t i; - for (i = 0; i < size; i++) { - uint16_t c = buf[i]; - if (c == '-' || c == '_') break; - } - if (i == 2) { - bits = uint8_t(buf[0]) | (uint8_t(buf[1]) << 8); - } else if (i == 3) { - bits = uint8_t(buf[0]) | (uint8_t(buf[1]) << 8) | (uint8_t(buf[2]) << 16); - } else { - mBits = kUnsupportedLanguage; - // We don't understand anything other than two-letter or three-letter - // language codes, so we skip parsing the rest of the string. - return; - } - size_t next; - for (i++; i < size; i = next + 1) { - for (next = i; next < size; next++) { - uint16_t c = buf[next]; - if (c == '-' || c == '_') break; - } - if (next - i == 4) { - if (buf[i] == 'H' && buf[i+1] == 'a' && buf[i+2] == 'n') { - if (buf[i+3] == 's') { - bits |= kHansFlag; - } else if (buf[i+3] == 't') { - bits |= kHantFlag; - } - } else if (buf[i] == 'Q' && buf[i+1] == 'a' && buf[i+2] == 'a'&& buf[i+3] == 'e') { - bits |= kEmojiFlag; - } - } - // TODO: this might be a good place to infer script from country (zh_TW -> Hant), - // but perhaps it's up to the client to do that, before passing a string. - } - mBits = bits; -} - -std::string FontLanguage::getString() const { - if (mBits == kUnsupportedLanguage) { - return "und"; - } - char buf[16]; - size_t i = 0; - if (mBits & kBaseLangMask) { - buf[i++] = mBits & 0xFFu; - buf[i++] = (mBits >> 8) & 0xFFu; - char third_letter = (mBits >> 16) & 0xFFu; - if (third_letter != 0) buf[i++] = third_letter; - } - if (mBits & kScriptMask) { - if (!i) { - // This should not happen, but as it apparently has, we fill the language code part - // with "und". - buf[i++] = 'u'; - buf[i++] = 'n'; - buf[i++] = 'd'; - } - buf[i++] = '-'; - if (mBits & kEmojiFlag) { - buf[i++] = 'Q'; - buf[i++] = 'a'; - buf[i++] = 'a'; - buf[i++] = 'e'; - } else { - buf[i++] = 'H'; - buf[i++] = 'a'; - buf[i++] = 'n'; - buf[i++] = (mBits & kHansFlag) ? 's' : 't'; - } - } - return std::string(buf, i); -} - -int FontLanguage::match(const FontLanguage other) const { - return *this == other; -} - -FontLanguages::FontLanguages(const char* buf, size_t size) { - std::unordered_set seen; - mLangs.clear(); - const char* bufEnd = buf + size; - const char* lastStart = buf; - bool isLastLang = false; - while (true) { - const char* commaLoc = static_cast( - memchr(lastStart, ',', bufEnd - lastStart)); - if (commaLoc == NULL) { - commaLoc = bufEnd; - isLastLang = true; - } - FontLanguage lang(lastStart, commaLoc - lastStart); - if (isLastLang && mLangs.size() == 0) { - // Make sure the list has at least one member - mLangs.push_back(lang); - return; - } - uint32_t bits = lang.bits(); - if (bits != FontLanguage::kUnsupportedLanguage && seen.count(bits) == 0) { - mLangs.push_back(lang); - if (isLastLang) return; - seen.insert(bits); - } - if (isLastLang) return; - lastStart = commaLoc + 1; - } -} - FontStyle::FontStyle(int variant, int weight, bool italic) : FontStyle(FontLanguageListCache::kEmptyListId, variant, weight, italic) { } @@ -177,6 +65,9 @@ uint32_t FontStyle::pack(int variant, int weight, bool italic) { return (weight & kWeightMask) | (italic ? kItalicMask : 0) | (variant << kVariantShift); } +FontFamily::FontFamily(int variant) : FontFamily(FontLanguageListCache::kEmptyListId, variant) { +} + FontFamily::~FontFamily() { for (size_t i = 0; i < mFonts.size(); i++) { mFonts[i].typeface->UnrefLocked(); @@ -210,7 +101,8 @@ void FontFamily::addFont(MinikinFont* typeface, FontStyle style) { addFontLocked(typeface, style); } -void FontFamily::addFontLocked(MinikinFont* typeface, FontStyle style) { typeface->RefLocked(); +void FontFamily::addFontLocked(MinikinFont* typeface, FontStyle style) { + typeface->RefLocked(); mFonts.push_back(Font(typeface, style)); mCoverageValid = false; } diff --git a/engine/src/flutter/libs/minikin/FontLanguage.cpp b/engine/src/flutter/libs/minikin/FontLanguage.cpp new file mode 100644 index 0000000000..3c12e06c6f --- /dev/null +++ b/engine/src/flutter/libs/minikin/FontLanguage.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Minikin" + +#include "FontLanguage.h" + +#include +#include + +namespace android { + +#define SCRIPT_TAG(c1, c2, c3, c4) \ + ((uint32_t)(c1)) << 24 | ((uint32_t)(c2)) << 16 | ((uint32_t)(c3)) << 8 | ((uint32_t)(c4)) + +// Parse BCP 47 language identifier into internal structure +FontLanguage::FontLanguage(const char* buf, size_t length) : FontLanguage() { + size_t i; + for (i = 0; i < length; i++) { + char c = buf[i]; + if (c == '-' || c == '_') break; + } + if (i == 2 || i == 3) { // only accept two or three letter language code. + mLanguage = buf[0] | (buf[1] << 8) | ((i == 3) ? (buf[2] << 16) : 0); + } else { + // We don't understand anything other than two-letter or three-letter + // language codes, so we skip parsing the rest of the string. + mLanguage = 0ul; + return; + } + + size_t next; + for (i++; i < length; i = next + 1) { + for (next = i; next < length; next++) { + char c = buf[next]; + if (c == '-' || c == '_') break; + } + if (next - i == 4 && 'A' <= buf[i] && buf[i] <= 'Z') { + mScript = SCRIPT_TAG(buf[i], buf[i + 1], buf[i + 2], buf[i + 3]); + } + } + + mSubScriptBits = scriptToSubScriptBits(mScript); +} + +//static +uint8_t FontLanguage::scriptToSubScriptBits(uint32_t script) { + uint8_t subScriptBits = 0u; + switch (script) { + case SCRIPT_TAG('H', 'a', 'n', 'g'): + subScriptBits = kHangulFlag; + break; + case SCRIPT_TAG('H', 'a', 'n', 'i'): + subScriptBits = kHanFlag; + break; + case SCRIPT_TAG('H', 'a', 'n', 's'): + subScriptBits = kHanFlag | kSimplifiedChineseFlag; + break; + case SCRIPT_TAG('H', 'a', 'n', 't'): + subScriptBits = kHanFlag | kTraditionalChineseFlag; + break; + case SCRIPT_TAG('H', 'i', 'r', 'a'): + subScriptBits = kHiraganaFlag; + break; + case SCRIPT_TAG('H', 'r', 'k', 't'): + subScriptBits = kKatakanaFlag | kHiraganaFlag; + break; + case SCRIPT_TAG('J', 'p', 'a', 'n'): + subScriptBits = kHanFlag | kKatakanaFlag | kHiraganaFlag; + break; + case SCRIPT_TAG('K', 'a', 'n', 'a'): + subScriptBits = kKatakanaFlag; + break; + case SCRIPT_TAG('K', 'o', 'r', 'e'): + subScriptBits = kHanFlag | kHangulFlag; + break; + case SCRIPT_TAG('Q', 'a', 'a', 'e'): + subScriptBits = kEmojiFlag; + break; + } + return subScriptBits; +} + +std::string FontLanguage::getString() const { + if (mLanguage == 0ul) { + return "und"; + } + char buf[16]; + size_t i = 0; + buf[i++] = mLanguage & 0xFF ; + buf[i++] = (mLanguage >> 8) & 0xFF; + char third_letter = (mLanguage >> 16) & 0xFF; + if (third_letter != 0) buf[i++] = third_letter; + if (mScript != 0) { + buf[i++] = '-'; + buf[i++] = (mScript >> 24) & 0xFFu; + buf[i++] = (mScript >> 16) & 0xFFu; + buf[i++] = (mScript >> 8) & 0xFFu; + buf[i++] = mScript & 0xFFu; + } + return std::string(buf, i); +} + +bool FontLanguage::isEqualScript(const FontLanguage other) const { + return other.mScript == mScript; +} + +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; +} + +int FontLanguage::match(const FontLanguage other) const { + // TODO: Use script for matching. + return *this == other; +} + +#undef SCRIPT_TAG +} // namespace android diff --git a/engine/src/flutter/libs/minikin/FontLanguage.h b/engine/src/flutter/libs/minikin/FontLanguage.h new file mode 100644 index 0000000000..abe7d13179 --- /dev/null +++ b/engine/src/flutter/libs/minikin/FontLanguage.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINIKIN_FONT_LANGUAGE_H +#define MINIKIN_FONT_LANGUAGE_H + +#include +#include + +#include + +namespace android { + +// FontLanguage is a compact representation of a BCP 47 language tag. It +// does not capture all possible information, only what directly affects +// font rendering. +struct FontLanguage { +public: + // Default constructor creates the unsupported language. + FontLanguage() : mScript(0ul), mLanguage(0ul), mSubScriptBits(0ul) {} + + // Parse from string + FontLanguage(const char* buf, size_t length); + + bool operator==(const FontLanguage other) const { + return !isUnsupported() && isEqualScript(other) && isEqualLanguage(other); + } + + bool operator!=(const FontLanguage other) const { + return !(*this == other); + } + + 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; + + // Returns true if this script supports the given script. For example, ja-Jpan supports Hira, + // ja-Hira doesn't support Jpan. + bool supportsHbScript(hb_script_t script) const; + + std::string getString() const; + + // 0 = no match, 1 = language matches + int match(const FontLanguage other) const; + + uint64_t getIdentifier() const { return (uint64_t)mScript << 32 | (uint64_t)mLanguage; } + +private: + // ISO 15924 compliant script code. The 4 chars script code are packed into a 32 bit integer. + uint32_t mScript; + + // ISO 639-1 or ISO 639-2 compliant language code. + // The two or three letter language code is packed into 32 bit integer. + // mLanguage = 0 means the FontLanguage is unsupported. + uint32_t mLanguage; + + // For faster comparing, use 7 bits for specific scripts. + static const uint8_t kEmojiFlag = 1u; + static const uint8_t kHanFlag = 1u << 1; + static const uint8_t kHangulFlag = 1u << 2; + static const uint8_t kHiraganaFlag = 1u << 3; + static const uint8_t kKatakanaFlag = 1u << 4; + static const uint8_t kSimplifiedChineseFlag = 1u << 5; + static const uint8_t kTraditionalChineseFlag = 1u << 6; + uint8_t mSubScriptBits; + + static uint8_t scriptToSubScriptBits(uint32_t script); +}; + +typedef std::vector FontLanguages; + +} // namespace android + +#endif // MINIKIN_FONT_LANGUAGE_H diff --git a/engine/src/flutter/libs/minikin/FontLanguageListCache.cpp b/engine/src/flutter/libs/minikin/FontLanguageListCache.cpp index e1c2343dda..2d64998e7a 100644 --- a/engine/src/flutter/libs/minikin/FontLanguageListCache.cpp +++ b/engine/src/flutter/libs/minikin/FontLanguageListCache.cpp @@ -19,13 +19,92 @@ #include "FontLanguageListCache.h" #include +#include +#include #include "MinikinInternal.h" +#include "FontLanguage.h" namespace android { const uint32_t FontLanguageListCache::kEmptyListId; +// Returns the text length of output. +static size_t toLanguageTag(char* output, size_t outSize, const std::string& locale) { + output[0] = '\0'; + if (locale.empty()) { + return 0; + } + + size_t outLength = 0; + UErrorCode uErr = U_ZERO_ERROR; + outLength = uloc_canonicalize(locale.c_str(), output, outSize, &uErr); + if (U_FAILURE(uErr)) { + // unable to build a proper language identifier + ALOGD("uloc_canonicalize(\"%s\") failed: %s", locale.c_str(), u_errorName(uErr)); + output[0] = '\0'; + return 0; + } + + // Preserve "und" and "und-****" since uloc_addLikelySubtags changes "und" to "en-Latn-US". + if (strncmp(output, "und", 3) == 0 && + (outLength == 3 || (outLength == 8 && output[3] == '_'))) { + return outLength; + } + + char likelyChars[ULOC_FULLNAME_CAPACITY]; + uErr = U_ZERO_ERROR; + uloc_addLikelySubtags(output, likelyChars, ULOC_FULLNAME_CAPACITY, &uErr); + if (U_FAILURE(uErr)) { + // unable to build a proper language identifier + ALOGD("uloc_addLikelySubtags(\"%s\") failed: %s", output, u_errorName(uErr)); + output[0] = '\0'; + return 0; + } + + uErr = U_ZERO_ERROR; + outLength = uloc_toLanguageTag(likelyChars, output, outSize, FALSE, &uErr); + if (U_FAILURE(uErr)) { + // unable to build a proper language identifier + ALOGD("uloc_toLanguageTag(\"%s\") failed: %s", likelyChars, u_errorName(uErr)); + output[0] = '\0'; + return 0; + } +#ifdef VERBOSE_DEBUG + ALOGD("ICU normalized '%s' to '%s'", locale.c_str(), output); +#endif + return outLength; +} + +static FontLanguages constructFontLanguages(const std::string& input) { + FontLanguages result; + size_t currentIdx = 0; + size_t commaLoc = 0; + char langTag[ULOC_FULLNAME_CAPACITY]; + std::unordered_set seen; + std::string locale(input.size(), 0); + + while ((commaLoc = input.find_first_of(',', currentIdx)) != std::string::npos) { + locale.assign(input, currentIdx, commaLoc - currentIdx); + currentIdx = commaLoc + 1; + 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); + 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); + } + return result; +} + // static uint32_t FontLanguageListCache::getId(const std::string& languages) { FontLanguageListCache* inst = FontLanguageListCache::getInstance(); @@ -37,7 +116,11 @@ uint32_t FontLanguageListCache::getId(const std::string& languages) { // Given language list is not in cache. Insert it and return newly assigned ID. const uint32_t nextId = inst->mLanguageLists.size(); - inst->mLanguageLists.push_back(FontLanguages(languages.c_str(), languages.size())); + FontLanguages fontLanguages = constructFontLanguages(languages); + if (fontLanguages.empty()) { + return kEmptyListId; + } + inst->mLanguageLists.push_back(fontLanguages); inst->mLanguageListLookupTable.insert(std::make_pair(languages, nextId)); return nextId; } @@ -56,8 +139,9 @@ FontLanguageListCache* FontLanguageListCache::getInstance() { if (instance == nullptr) { instance = new FontLanguageListCache(); - // Insert an empty language list for mapping empty language list to kEmptyListId. - instance->mLanguageLists.push_back(FontLanguages()); + // 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. + instance->mLanguageLists.push_back(FontLanguages({FontLanguage()})); instance->mLanguageListLookupTable.insert(std::make_pair("", kEmptyListId)); } return instance; diff --git a/engine/src/flutter/libs/minikin/FontLanguageListCache.h b/engine/src/flutter/libs/minikin/FontLanguageListCache.h index 7d627b562e..c961882f0a 100644 --- a/engine/src/flutter/libs/minikin/FontLanguageListCache.h +++ b/engine/src/flutter/libs/minikin/FontLanguageListCache.h @@ -20,6 +20,7 @@ #include #include +#include "FontLanguage.h" namespace android { diff --git a/engine/src/flutter/libs/minikin/Layout.cpp b/engine/src/flutter/libs/minikin/Layout.cpp index af5e6fe75a..2e206a20fc 100644 --- a/engine/src/flutter/libs/minikin/Layout.cpp +++ b/engine/src/flutter/libs/minikin/Layout.cpp @@ -34,6 +34,7 @@ #include #include +#include "FontLanguage.h" #include "FontLanguageListCache.h" #include "LayoutUtils.h" #include "HbFaceCache.h" @@ -746,9 +747,15 @@ void Layout::doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t const FontLanguages& langList = FontLanguageListCache::getById(ctx->style.getLanguageListId()); if (langList.size() != 0) { - // TODO: use all languages in langList. - string lang = langList[0].getString(); - hb_buffer_set_language(buffer, hb_language_from_string(lang.c_str(), -1)); + const FontLanguage* hbLanguage = &langList[0]; + for (size_t i = 0; i < langList.size(); ++i) { + if (langList[i].supportsHbScript(script)) { + hbLanguage = &langList[i]; + break; + } + } + hb_buffer_set_language(buffer, + hb_language_from_string(hbLanguage->getString().c_str(), -1)); } hb_buffer_add_utf16(buffer, buf, bufSize, srunstart + start, srunend - srunstart); if (ctx->paint.hyphenEdit.hasHyphen() && srunend > srunstart) { diff --git a/engine/src/flutter/tests/FontCollectionItemizeTest.cpp b/engine/src/flutter/tests/FontCollectionItemizeTest.cpp index 85d76afccb..cf9b704efa 100644 --- a/engine/src/flutter/tests/FontCollectionItemizeTest.cpp +++ b/engine/src/flutter/tests/FontCollectionItemizeTest.cpp @@ -16,7 +16,9 @@ #include +#include "FontLanguage.h" #include "FontTestUtils.h" +#include "ICUTestBase.h" #include "MinikinFontForTest.h" #include "UnicodeUtils.h" @@ -42,6 +44,8 @@ const char kColorEmojiFont[] = kTestFontDir "ColorEmojiFont.ttf"; const char kTextEmojiFont[] = kTestFontDir "TextEmojiFont.ttf"; const char kMixedEmojiFont[] = kTestFontDir "ColorTextMixedEmojiFont.ttf"; +typedef ICUTestBase FontCollectionItemizeTest; + // Utility function for calling itemize function. void itemize(FontCollection* collection, const char* str, FontStyle style, std::vector* result) { @@ -60,7 +64,7 @@ const std::string& getFontPath(const FontCollection::Run& run) { return ((MinikinFontForTest*)run.fakedFont.font)->fontPath(); } -TEST(FontCollectionItemizeTest, itemize_latin) { +TEST_F(FontCollectionItemizeTest, itemize_latin) { std::unique_ptr collection = getFontCollection(kTestFontDir, kItemizeFontXml); std::vector runs; @@ -130,7 +134,7 @@ TEST(FontCollectionItemizeTest, itemize_latin) { EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic()); } -TEST(FontCollectionItemizeTest, itemize_emoji) { +TEST_F(FontCollectionItemizeTest, itemize_emoji) { std::unique_ptr collection = getFontCollection(kTestFontDir, kItemizeFontXml); std::vector runs; @@ -191,7 +195,7 @@ TEST(FontCollectionItemizeTest, itemize_emoji) { EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic()); } -TEST(FontCollectionItemizeTest, itemize_non_latin) { +TEST_F(FontCollectionItemizeTest, itemize_non_latin) { std::unique_ptr collection = getFontCollection(kTestFontDir, kItemizeFontXml); std::vector runs; @@ -280,7 +284,7 @@ TEST(FontCollectionItemizeTest, itemize_non_latin) { EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic()); } -TEST(FontCollectionItemizeTest, itemize_mixed) { +TEST_F(FontCollectionItemizeTest, itemize_mixed) { std::unique_ptr collection = getFontCollection(kTestFontDir, kItemizeFontXml); std::vector runs; @@ -319,7 +323,7 @@ TEST(FontCollectionItemizeTest, itemize_mixed) { EXPECT_FALSE(runs[4].fakedFont.fakery.isFakeItalic()); } -TEST(FontCollectionItemizeTest, itemize_variationSelector) { +TEST_F(FontCollectionItemizeTest, itemize_variationSelector) { std::unique_ptr collection = getFontCollection(kTestFontDir, kItemizeFontXml); std::vector runs; @@ -458,7 +462,7 @@ TEST(FontCollectionItemizeTest, itemize_variationSelector) { EXPECT_EQ(kLatinFont, getFontPath(runs[0])); } -TEST(FontCollectionItemizeTest, itemize_variationSelectorSupplement) { +TEST_F(FontCollectionItemizeTest, itemize_variationSelectorSupplement) { std::unique_ptr collection = getFontCollection(kTestFontDir, kItemizeFontXml); std::vector runs; @@ -583,7 +587,7 @@ TEST(FontCollectionItemizeTest, itemize_variationSelectorSupplement) { EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontPath(runs[0])); } -TEST(FontCollectionItemizeTest, itemize_no_crash) { +TEST_F(FontCollectionItemizeTest, itemize_no_crash) { std::unique_ptr collection = getFontCollection(kTestFontDir, kItemizeFontXml); std::vector runs; @@ -607,7 +611,7 @@ TEST(FontCollectionItemizeTest, itemize_no_crash) { itemize(collection.get(), "U+FE00 U+302D U+E0100", FontStyle(), &runs); } -TEST(FontCollectionItemizeTest, itemize_fakery) { +TEST_F(FontCollectionItemizeTest, itemize_fakery) { std::unique_ptr collection = getFontCollection(kTestFontDir, kItemizeFontXml); std::vector runs; @@ -647,18 +651,18 @@ TEST(FontCollectionItemizeTest, itemize_fakery) { EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeItalic()); } -TEST(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) { +TEST_F(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) { // kVSTestFont supports U+717D U+FE02 but doesn't support U+717D. // kVSTestFont should be selected for U+717D U+FE02 even if it does not support the base code // point. const std::string kVSTestFont = kTestFontDir "VarioationSelectorTest-Regular.ttf"; std::vector families; - FontFamily* family1 = new FontFamily(FontLanguage(), android::VARIANT_DEFAULT); + FontFamily* family1 = new FontFamily(android::VARIANT_DEFAULT); family1->addFont(new MinikinFontForTest(kLatinFont)); families.push_back(family1); - FontFamily* family2 = new FontFamily(FontLanguage(), android::VARIANT_DEFAULT); + FontFamily* family2 = new FontFamily(android::VARIANT_DEFAULT); family2->addFont(new MinikinFontForTest(kVSTestFont)); families.push_back(family2); @@ -676,7 +680,7 @@ TEST(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) { family2->Unref(); } -TEST(FontCollectionItemizeTest, itemize_emojiSelection) { +TEST_F(FontCollectionItemizeTest, itemize_emojiSelection) { std::unique_ptr collection = getFontCollection(kTestFontDir, kEmojiXmlFile); std::vector runs; @@ -748,7 +752,7 @@ TEST(FontCollectionItemizeTest, itemize_emojiSelection) { EXPECT_TRUE(runs[0].fakedFont.font == NULL || kNoGlyphFont == getFontPath(runs[0])); } -TEST(FontCollectionItemizeTest, itemize_emojiSelection_withFE0E) { +TEST_F(FontCollectionItemizeTest, itemize_emojiSelection_withFE0E) { std::unique_ptr collection = getFontCollection(kTestFontDir, kEmojiXmlFile); std::vector runs; @@ -830,7 +834,7 @@ TEST(FontCollectionItemizeTest, itemize_emojiSelection_withFE0E) { EXPECT_EQ(kMixedEmojiFont, getFontPath(runs[0])); } -TEST(FontCollectionItemizeTest, itemize_emojiSelection_withFE0F) { +TEST_F(FontCollectionItemizeTest, itemize_emojiSelection_withFE0F) { std::unique_ptr collection = getFontCollection(kTestFontDir, kEmojiXmlFile); std::vector runs; diff --git a/engine/src/flutter/tests/FontFamilyTest.cpp b/engine/src/flutter/tests/FontFamilyTest.cpp index fc44e6c636..ac7616fada 100644 --- a/engine/src/flutter/tests/FontFamilyTest.cpp +++ b/engine/src/flutter/tests/FontFamilyTest.cpp @@ -17,74 +17,261 @@ #include #include + +#include + #include "FontLanguageListCache.h" +#include "ICUTestBase.h" #include "MinikinFontForTest.h" #include "MinikinInternal.h" namespace android { -TEST(FontLanguagesTest, basicTests) { +typedef ICUTestBase FontLanguagesTest; +typedef ICUTestBase FontLanguageTest; + +static FontLanguages createFontLanguages(const std::string& input) { + uint32_t langId = FontLanguageListCache::getId(input); + return FontLanguageListCache::getById(langId); +} + +static FontLanguage createFontLanguage(const std::string& input) { + uint32_t langId = FontLanguageListCache::getId(input); + return FontLanguageListCache::getById(langId)[0]; +} + +TEST_F(FontLanguageTest, basicTests) { + FontLanguage defaultLang; + FontLanguage emptyLang("", 0); + FontLanguage english = createFontLanguage("en"); + FontLanguage french = createFontLanguage("fr"); + FontLanguage und = createFontLanguage("und"); + FontLanguage undQaae = createFontLanguage("und-Qaae"); + + EXPECT_EQ(english, english); + EXPECT_EQ(french, french); + + EXPECT_TRUE(defaultLang != defaultLang); + EXPECT_TRUE(emptyLang != emptyLang); + EXPECT_TRUE(defaultLang != emptyLang); + EXPECT_TRUE(defaultLang != und); + EXPECT_TRUE(emptyLang != und); + EXPECT_TRUE(english != defaultLang); + EXPECT_TRUE(english != emptyLang); + EXPECT_TRUE(english != french); + EXPECT_TRUE(english != undQaae); + EXPECT_TRUE(und != undQaae); + EXPECT_TRUE(english != und); + + EXPECT_TRUE(defaultLang.isUnsupported()); + EXPECT_TRUE(emptyLang.isUnsupported()); + + EXPECT_FALSE(english.isUnsupported()); + EXPECT_FALSE(french.isUnsupported()); + EXPECT_FALSE(und.isUnsupported()); + EXPECT_FALSE(undQaae.isUnsupported()); +} + +TEST_F(FontLanguageTest, getStringTest) { + EXPECT_EQ("en-Latn", createFontLanguage("en").getString()); + EXPECT_EQ("en-Latn", createFontLanguage("en-Latn").getString()); + + // Capitalized language code or lowercased script should be normalized. + EXPECT_EQ("en-Latn", createFontLanguage("EN-LATN").getString()); + EXPECT_EQ("en-Latn", createFontLanguage("EN-latn").getString()); + EXPECT_EQ("en-Latn", createFontLanguage("en-latn").getString()); + + // Invalid script should be kept. + EXPECT_EQ("en-Xyzt", createFontLanguage("en-xyzt").getString()); + + EXPECT_EQ("en-Latn", createFontLanguage("en-Latn-US").getString()); + EXPECT_EQ("ja-Jpan", createFontLanguage("ja").getString()); + EXPECT_EQ("und", createFontLanguage("und").getString()); + EXPECT_EQ("und", createFontLanguage("UND").getString()); + EXPECT_EQ("und", createFontLanguage("Und").getString()); + EXPECT_EQ("und-Qaae", createFontLanguage("und-Qaae").getString()); + EXPECT_EQ("und-Qaae", createFontLanguage("Und-QAAE").getString()); + EXPECT_EQ("und-Qaae", createFontLanguage("Und-qaae").getString()); + + EXPECT_EQ("de-Latn", createFontLanguage("de-1901").getString()); + + // This is not a necessary desired behavior, just known behavior. + EXPECT_EQ("en-Latn", createFontLanguage("und-Abcdefgh").getString()); +} + +TEST_F(FontLanguageTest, ScriptEqualTest) { + EXPECT_TRUE(createFontLanguage("en").isEqualScript(createFontLanguage("en"))); + EXPECT_TRUE(createFontLanguage("en-Latn").isEqualScript(createFontLanguage("en"))); + EXPECT_TRUE(createFontLanguage("jp-Latn").isEqualScript(createFontLanguage("en-Latn"))); + EXPECT_TRUE(createFontLanguage("en-Jpan").isEqualScript(createFontLanguage("en-Jpan"))); + + EXPECT_FALSE(createFontLanguage("en-Jpan").isEqualScript(createFontLanguage("en-Hira"))); + EXPECT_FALSE(createFontLanguage("en-Jpan").isEqualScript(createFontLanguage("en-Hani"))); +} + +TEST_F(FontLanguageTest, ScriptMatchTest) { + const bool SUPPORTED = true; + const bool NOT_SUPPORTED = false; + + struct TestCase { + const std::string baseScript; + const std::string requestedScript; + bool isSupported; + } testCases[] = { + // Same scripts + { "en-Latn", "Latn", SUPPORTED }, + { "ja-Jpan", "Jpan", SUPPORTED }, + { "ja-Hira", "Hira", SUPPORTED }, + { "ja-Kana", "Kana", SUPPORTED }, + { "ja-Hrkt", "Hrkt", SUPPORTED }, + { "zh-Hans", "Hans", SUPPORTED }, + { "zh-Hant", "Hant", SUPPORTED }, + { "zh-Hani", "Hani", SUPPORTED }, + { "ko-Kore", "Kore", SUPPORTED }, + { "ko-Hang", "Hang", SUPPORTED }, + + // Japanese supports Hiragana, Katakanara, etc. + { "ja-Jpan", "Hira", SUPPORTED }, + { "ja-Jpan", "Kana", SUPPORTED }, + { "ja-Jpan", "Hrkt", SUPPORTED }, + { "ja-Hrkt", "Hira", SUPPORTED }, + { "ja-Hrkt", "Kana", SUPPORTED }, + + // Chinese supports Han. + { "zh-Hans", "Hani", SUPPORTED }, + { "zh-Hant", "Hani", SUPPORTED }, + + // Korean supports Hangul. + { "ko-Kore", "Hang", SUPPORTED }, + + // Different scripts + { "ja-Jpan", "Latn", NOT_SUPPORTED }, + { "en-Latn", "Jpan", NOT_SUPPORTED }, + { "ja-Jpan", "Hant", NOT_SUPPORTED }, + { "zh-Hant", "Jpan", NOT_SUPPORTED }, + { "ja-Jpan", "Hans", NOT_SUPPORTED }, + { "zh-Hans", "Jpan", NOT_SUPPORTED }, + { "ja-Jpan", "Kore", NOT_SUPPORTED }, + { "ko-Kore", "Jpan", NOT_SUPPORTED }, + { "zh-Hans", "Hant", NOT_SUPPORTED }, + { "zh-Hant", "Hans", NOT_SUPPORTED }, + { "zh-Hans", "Kore", NOT_SUPPORTED }, + { "ko-Kore", "Hans", NOT_SUPPORTED }, + { "zh-Hant", "Kore", NOT_SUPPORTED }, + { "ko-Kore", "Hant", NOT_SUPPORTED }, + + // Hiragana doesn't support Japanese, etc. + { "ja-Hira", "Jpan", NOT_SUPPORTED }, + { "ja-Kana", "Jpan", NOT_SUPPORTED }, + { "ja-Hrkt", "Jpan", NOT_SUPPORTED }, + { "ja-Hani", "Jpan", NOT_SUPPORTED }, + { "ja-Hira", "Hrkt", NOT_SUPPORTED }, + { "ja-Kana", "Hrkt", NOT_SUPPORTED }, + { "ja-Hani", "Hrkt", NOT_SUPPORTED }, + { "ja-Hani", "Hira", NOT_SUPPORTED }, + { "ja-Hani", "Kana", NOT_SUPPORTED }, + + // Kanji doesn't support Chinese, etc. + { "zh-Hani", "Hant", NOT_SUPPORTED }, + { "zh-Hani", "Hans", NOT_SUPPORTED }, + + // Hangul doesn't support Korean, etc. + { "ko-Hang", "Kore", NOT_SUPPORTED }, + { "ko-Hani", "Kore", NOT_SUPPORTED }, + { "ko-Hani", "Hang", NOT_SUPPORTED }, + { "ko-Hang", "Hani", NOT_SUPPORTED }, + }; + + for (auto testCase : testCases) { + hb_script_t script = hb_script_from_iso15924_tag( + HB_TAG(testCase.requestedScript[0], testCase.requestedScript[1], + testCase.requestedScript[2], testCase.requestedScript[3])); + if (testCase.isSupported) { + EXPECT_TRUE( + createFontLanguage(testCase.baseScript).supportsHbScript(script)) + << testCase.baseScript << " should support " << testCase.requestedScript; + } else { + EXPECT_FALSE( + createFontLanguage(testCase.baseScript).supportsHbScript(script)) + << testCase.baseScript << " shouldn't support " << testCase.requestedScript; + } + } +} + +TEST_F(FontLanguagesTest, basicTests) { FontLanguages emptyLangs; EXPECT_EQ(0u, emptyLangs.size()); - FontLanguage english("en", 2); - FontLanguages singletonLangs("en", 2); + FontLanguage english = createFontLanguage("en"); + FontLanguages singletonLangs = createFontLanguages("en"); EXPECT_EQ(1u, singletonLangs.size()); EXPECT_EQ(english, singletonLangs[0]); - FontLanguage french("fr", 2); - FontLanguages twoLangs("en,fr", 5); + FontLanguage french = createFontLanguage("fr"); + FontLanguages twoLangs = createFontLanguages("en,fr"); EXPECT_EQ(2u, twoLangs.size()); EXPECT_EQ(english, twoLangs[0]); EXPECT_EQ(french, twoLangs[1]); } -TEST(FontLanguagesTest, unsupportedLanguageTests) { - FontLanguage unsupportedLang("x-example", 9); +TEST_F(FontLanguagesTest, unsupportedLanguageTests) { + FontLanguage unsupportedLang = createFontLanguage("abcd"); ASSERT_TRUE(unsupportedLang.isUnsupported()); - FontLanguages oneUnsupported("x-example", 9); + FontLanguages oneUnsupported = createFontLanguages("abcd-example"); EXPECT_EQ(1u, oneUnsupported.size()); EXPECT_TRUE(oneUnsupported[0].isUnsupported()); - FontLanguages twoUnsupporteds("x-example,x-example", 19); + FontLanguages twoUnsupporteds = createFontLanguages("abcd-example,abcd-example"); EXPECT_EQ(1u, twoUnsupporteds.size()); EXPECT_TRUE(twoUnsupporteds[0].isUnsupported()); - FontLanguage english("en", 2); - FontLanguages firstUnsupported("x-example,en", 12); + FontLanguage english = createFontLanguage("en"); + FontLanguages firstUnsupported = createFontLanguages("abcd-example,en"); EXPECT_EQ(1u, firstUnsupported.size()); EXPECT_EQ(english, firstUnsupported[0]); - FontLanguages lastUnsupported("en,x-example", 12); + FontLanguages lastUnsupported = createFontLanguages("en,abcd-example"); EXPECT_EQ(1u, lastUnsupported.size()); EXPECT_EQ(english, lastUnsupported[0]); } -TEST(FontLanguagesTest, repeatedLanguageTests) { - FontLanguage english("en", 2); - FontLanguage englishInLatn("en-Latn", 2); +TEST_F(FontLanguagesTest, repeatedLanguageTests) { + FontLanguage english = createFontLanguage("en"); + FontLanguage french = createFontLanguage("fr"); + FontLanguage englishInLatn = createFontLanguage("en-Latn"); ASSERT_TRUE(english == englishInLatn); - FontLanguages langs("en,en-Latn", 10); + FontLanguages langs = createFontLanguages("en,en-Latn"); EXPECT_EQ(1u, langs.size()); EXPECT_EQ(english, langs[0]); + + // Country codes are ignored. + FontLanguages fr = createFontLanguages("fr,fr-CA,fr-FR"); + EXPECT_EQ(1u, fr.size()); + EXPECT_EQ(french, fr[0]); + + // The order should be kept. + langs = createFontLanguages("en,fr,en-Latn"); + EXPECT_EQ(2u, langs.size()); + EXPECT_EQ(english, langs[0]); + EXPECT_EQ(french, langs[1]); } -TEST(FontLanguagesTest, undEmojiTests) { - FontLanguage emoji("und-Qaae", 8); +TEST_F(FontLanguagesTest, undEmojiTests) { + FontLanguage emoji = createFontLanguage("und-Qaae"); EXPECT_TRUE(emoji.hasEmojiFlag()); - FontLanguage und("und", 3); + FontLanguage und = createFontLanguage("und"); EXPECT_FALSE(und.hasEmojiFlag()); EXPECT_FALSE(emoji == und); - FontLanguage undExample("und-example", 10); + FontLanguage undExample = createFontLanguage("und-example"); EXPECT_FALSE(undExample.hasEmojiFlag()); EXPECT_FALSE(emoji == undExample); } -TEST(FontLanguagesTest, registerLanguageListTest) { +TEST_F(FontLanguagesTest, registerLanguageListTest) { EXPECT_EQ(0UL, FontStyle::registerLanguageList("")); EXPECT_NE(0UL, FontStyle::registerLanguageList("en")); EXPECT_NE(0UL, FontStyle::registerLanguageList("jp")); @@ -122,9 +309,10 @@ TEST(FontLanguagesTest, registerLanguageListTest) { // U+717D U+E0103 (VS20) const char kVsTestFont[] = kTestFontDir "VarioationSelectorTest-Regular.ttf"; -class FontFamilyTest : public testing::Test { +class FontFamilyTest : public ICUTestBase { public: virtual void SetUp() override { + ICUTestBase::SetUp(); if (access(kVsTestFont, R_OK) != 0) { FAIL() << "Unable to read " << kVsTestFont << ". " << "Please prepare the test data directory. " diff --git a/engine/src/flutter/tests/FontLanguageListCacheTest.cpp b/engine/src/flutter/tests/FontLanguageListCacheTest.cpp index 29757fedce..f83988c1f5 100644 --- a/engine/src/flutter/tests/FontLanguageListCacheTest.cpp +++ b/engine/src/flutter/tests/FontLanguageListCacheTest.cpp @@ -17,11 +17,15 @@ #include #include + #include "FontLanguageListCache.h" +#include "ICUTestBase.h" namespace android { -TEST(FontLanguageListCacheTest, getId) { +typedef ICUTestBase FontLanguageListCacheTest; + +TEST_F(FontLanguageListCacheTest, getId) { EXPECT_EQ(0UL, FontLanguageListCache::getId("")); EXPECT_NE(0UL, FontStyle::registerLanguageList("en")); EXPECT_NE(0UL, FontStyle::registerLanguageList("jp")); @@ -42,11 +46,15 @@ TEST(FontLanguageListCacheTest, getId) { FontLanguageListCache::getId("en,zh-Hant")); } -TEST(FontLanguageListCacheTest, getById) { - FontLanguage english("en", 2); - FontLanguage japanese("jp", 2); +TEST_F(FontLanguageListCacheTest, getById) { + uint32_t enLangId = FontLanguageListCache::getId("en"); + uint32_t jpLangId = FontLanguageListCache::getId("jp"); + FontLanguage english = FontLanguageListCache::getById(enLangId)[0]; + FontLanguage japanese = FontLanguageListCache::getById(jpLangId)[0]; - EXPECT_EQ(0UL, FontLanguageListCache::getById(0).size()); + FontLanguages defLangs = FontLanguageListCache::getById(0); + EXPECT_EQ(1UL, defLangs.size()); + EXPECT_TRUE(defLangs[0].isUnsupported()); FontLanguages langs = FontLanguageListCache::getById(FontLanguageListCache::getId("en")); ASSERT_EQ(1UL, langs.size()); diff --git a/engine/src/flutter/tests/FontTestUtils.cpp b/engine/src/flutter/tests/FontTestUtils.cpp index 8e1d184adc..98dab5121b 100644 --- a/engine/src/flutter/tests/FontTestUtils.cpp +++ b/engine/src/flutter/tests/FontTestUtils.cpp @@ -20,6 +20,8 @@ #include #include + +#include "FontLanguage.h" #include "MinikinFontForTest.h" std::unique_ptr getFontCollection( @@ -44,9 +46,10 @@ std::unique_ptr getFontCollection( } xmlChar* lang = xmlGetProp(familyNode, (const xmlChar*)"lang"); + uint32_t langId = android::FontStyle::registerLanguageList( + std::string((const char*)lang, xmlStrlen(lang))); - android::FontFamily* family = new android::FontFamily( - android::FontLanguage((const char*)lang, xmlStrlen(lang)), variant); + android::FontFamily* family = new android::FontFamily(langId, variant); for (xmlNode* fontNode = familyNode->children; fontNode; fontNode = fontNode->next) { if (xmlStrcmp(fontNode->name, (const xmlChar*)"font") != 0) { diff --git a/engine/src/flutter/tests/ICUTestBase.h b/engine/src/flutter/tests/ICUTestBase.h new file mode 100644 index 0000000000..3bcfaf3694 --- /dev/null +++ b/engine/src/flutter/tests/ICUTestBase.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINIKIN_TEST_ICU_TEST_BASE_H +#define MINIKIN_TEST_ICU_TEST_BASE_H + +#include +#include +#include + +// low level file access for mapping ICU data +#include +#include +#include + +class ICUTestBase : public testing::Test { +protected: + virtual void SetUp() override { + const char* fn = "/system/usr/icu/" U_ICUDATA_NAME ".dat"; + int fd = open(fn, O_RDONLY); + ASSERT_NE(-1, fd); + struct stat sb; + ASSERT_EQ(0, fstat(fd, &sb)); + void* data = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); + + UErrorCode errorCode = U_ZERO_ERROR; + udata_setCommonData(data, &errorCode); + ASSERT_TRUE(U_SUCCESS(errorCode)); + u_init(&errorCode); + ASSERT_TRUE(U_SUCCESS(errorCode)); + } + + virtual void TearDown() override { + u_cleanup(); + } +}; + + +#endif // MINIKIN_TEST_ICU_TEST_BASE_H