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