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<FontLanguage>

- 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
This commit is contained in:
Seigo Nonaka
2015-12-14 18:33:23 -08:00
parent d41c16fb7e
commit bb5c10092c
14 changed files with 639 additions and 225 deletions

View File

@@ -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<FontLanguage> 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<Font> mFonts;

View File

@@ -21,6 +21,7 @@ minikin_src_files := \
CmapCoverage.cpp \
FontCollection.cpp \
FontFamily.cpp \
FontLanguage.cpp \
FontLanguageListCache.cpp \
GraphemeBreak.cpp \
HbFaceCache.cpp \

View File

@@ -22,6 +22,7 @@
#include "unicode/unistr.h"
#include "unicode/unorm2.h"
#include "FontLanguage.h"
#include "FontLanguageListCache.h"
#include "MinikinInternal.h"
#include <minikin/FontCollection.h>
@@ -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) {

View File

@@ -16,8 +16,6 @@
#define LOG_TAG "Minikin"
#include <unordered_set>
#include <cutils/log.h>
#include <stdlib.h>
#include <stdint.h>
@@ -28,6 +26,7 @@
#include <utils/JenkinsHash.h>
#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<uint32_t> seen;
mLangs.clear();
const char* bufEnd = buf + size;
const char* lastStart = buf;
bool isLastLang = false;
while (true) {
const char* commaLoc = static_cast<const char*>(
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;
}

View File

@@ -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 <hb.h>
#include <unicode/uloc.h>
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

View File

@@ -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 <string>
#include <vector>
#include <hb.h>
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<FontLanguage> FontLanguages;
} // namespace android
#endif // MINIKIN_FONT_LANGUAGE_H

View File

@@ -19,13 +19,92 @@
#include "FontLanguageListCache.h"
#include <cutils/log.h>
#include <unicode/uloc.h>
#include <unordered_set>
#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<uint64_t> 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;

View File

@@ -20,6 +20,7 @@
#include <unordered_map>
#include <minikin/FontFamily.h>
#include "FontLanguage.h"
namespace android {

View File

@@ -34,6 +34,7 @@
#include <hb-icu.h>
#include <hb-ot.h>
#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) {

View File

@@ -16,7 +16,9 @@
#include <gtest/gtest.h>
#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<FontCollection::Run>* 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<FontCollection> collection = getFontCollection(kTestFontDir, kItemizeFontXml);
std::vector<FontCollection::Run> 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<FontCollection> collection = getFontCollection(kTestFontDir, kItemizeFontXml);
std::vector<FontCollection::Run> 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<FontCollection> collection = getFontCollection(kTestFontDir, kItemizeFontXml);
std::vector<FontCollection::Run> 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<FontCollection> collection = getFontCollection(kTestFontDir, kItemizeFontXml);
std::vector<FontCollection::Run> 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<FontCollection> collection = getFontCollection(kTestFontDir, kItemizeFontXml);
std::vector<FontCollection::Run> 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<FontCollection> collection = getFontCollection(kTestFontDir, kItemizeFontXml);
std::vector<FontCollection::Run> 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<FontCollection> collection = getFontCollection(kTestFontDir, kItemizeFontXml);
std::vector<FontCollection::Run> 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<FontCollection> collection = getFontCollection(kTestFontDir, kItemizeFontXml);
std::vector<FontCollection::Run> 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<android::FontFamily*> 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<FontCollection> collection = getFontCollection(kTestFontDir, kEmojiXmlFile);
std::vector<FontCollection::Run> 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<FontCollection> collection = getFontCollection(kTestFontDir, kEmojiXmlFile);
std::vector<FontCollection::Run> 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<FontCollection> collection = getFontCollection(kTestFontDir, kEmojiXmlFile);
std::vector<FontCollection::Run> runs;

View File

@@ -17,74 +17,261 @@
#include <gtest/gtest.h>
#include <minikin/FontFamily.h>
#include <cutils/log.h>
#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. "

View File

@@ -17,11 +17,15 @@
#include <gtest/gtest.h>
#include <minikin/FontFamily.h>
#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());

View File

@@ -20,6 +20,8 @@
#include <minikin/FontFamily.h>
#include <cutils/log.h>
#include "FontLanguage.h"
#include "MinikinFontForTest.h"
std::unique_ptr<android::FontCollection> getFontCollection(
@@ -44,9 +46,10 @@ std::unique_ptr<android::FontCollection> 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) {

View File

@@ -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 <gtest/gtest.h>
#include <unicode/uclean.h>
#include <unicode/udata.h>
// low level file access for mapping ICU data
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
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