diff options
author | Raph Levien <raph@google.com> | 2014-05-29 22:38:29 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2014-05-29 22:38:29 +0000 |
commit | b4a27545571b842a73b5d68c1361c08498f36337 (patch) | |
tree | 5cc08f00c48aaa2b18921a8c4b2c5a9ded2f4e4a | |
parent | 7d190a2b771c37b6e3d45265e3a40019067813a6 (diff) | |
parent | 7b221d97b7b64dc5ce457e19666d55d042e22e62 (diff) | |
download | android_frameworks_minikin-b4a27545571b842a73b5d68c1361c08498f36337.tar.gz android_frameworks_minikin-b4a27545571b842a73b5d68c1361c08498f36337.tar.bz2 android_frameworks_minikin-b4a27545571b842a73b5d68c1361c08498f36337.zip |
am 7b221d97: Language and variant selection
* commit '7b221d97b7b64dc5ce457e19666d55d042e22e62':
Language and variant selection
-rw-r--r-- | include/minikin/CssParse.h | 23 | ||||
-rw-r--r-- | include/minikin/FontCollection.h | 3 | ||||
-rw-r--r-- | include/minikin/FontFamily.h | 61 | ||||
-rw-r--r-- | libs/minikin/CssParse.cpp | 53 | ||||
-rw-r--r-- | libs/minikin/FontCollection.cpp | 37 | ||||
-rw-r--r-- | libs/minikin/FontFamily.cpp | 41 | ||||
-rw-r--r-- | libs/minikin/Layout.cpp | 18 |
7 files changed, 214 insertions, 22 deletions
diff --git a/include/minikin/CssParse.h b/include/minikin/CssParse.h index 2dceb4a..519056d 100644 --- a/include/minikin/CssParse.h +++ b/include/minikin/CssParse.h @@ -25,26 +25,31 @@ namespace android { enum CssTag { unknown, fontSize, - fontWeight, fontStyle, - minikinHinting, + fontWeight, + cssLang, minikinBidi, + minikinHinting, + minikinVariant, }; const std::string cssTagNames[] = { "unknown", "font-size", - "font-weight", "font-style", - "-minikin-hinting", + "font-weight", + "lang", "-minikin-bidi", + "-minikin-hinting", + "-minikin-variant", }; class CssValue { public: enum Type { UNKNOWN, - FLOAT + FLOAT, + STRING }; enum Units { SCALAR, @@ -58,14 +63,20 @@ public: Type getType() const { return mType; } double getFloatValue() const { return floatValue; } int getIntValue() const { return floatValue; } + std::string getStringValue() const { return stringValue; } std::string toString(CssTag tag) const; void setFloatValue(double v) { mType = FLOAT; floatValue = v; } + void setStringValue(const std::string& v) { + mType = STRING; + stringValue = v; + } private: Type mType; double floatValue; + std::string stringValue; Units mUnits; }; @@ -85,4 +96,4 @@ private: } // namespace android -#endif // MINIKIN_CSS_PARSE_H
\ No newline at end of file +#endif // MINIKIN_CSS_PARSE_H diff --git a/include/minikin/FontCollection.h b/include/minikin/FontCollection.h index c6c2ed0..a350237 100644 --- a/include/minikin/FontCollection.h +++ b/include/minikin/FontCollection.h @@ -32,7 +32,8 @@ public: ~FontCollection(); - const FontFamily* getFamilyForChar(uint32_t ch) const; + const FontFamily* getFamilyForChar(uint32_t ch, FontLanguage lang, int variant) const; + class Run { public: // Do copy constructor, assignment, destructor so it can be used in vectors diff --git a/include/minikin/FontFamily.h b/include/minikin/FontFamily.h index a4fe9cb..6bdf5d6 100644 --- a/include/minikin/FontFamily.h +++ b/include/minikin/FontFamily.h @@ -25,6 +25,36 @@ 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; +public: + FontLanguage() : mBits(0) { } + + // Parse from string + FontLanguage(const char* buf, size_t size); + + bool operator==(const FontLanguage other) const { return mBits == other.mBits; } + + // 0 = no match, 1 = language matches, 2 = language and script match + int match(const FontLanguage other) const; + +private: + explicit FontLanguage(uint32_t bits) : mBits(bits) { } + + uint32_t bits() const { return mBits; } + + static const uint32_t kBaseLangMask = 0xffff; + static const uint32_t kScriptMask = (1 << 18) - (1 << 16); + static const uint32_t kHansFlag = 1 << 16; + static const uint32_t kHantFlag = 1 << 17; + uint32_t mBits; +}; + // FontStyle represents all style information needed to select an actual font // from a collection. The implementation is packed into a single 32-bit word // so it can be efficiently copied, embedded in other objects, etc. @@ -33,24 +63,44 @@ public: FontStyle(int weight = 4, bool italic = false) { bits = (weight & kWeightMask) | (italic ? kItalicMask : 0); } + FontStyle(FontLanguage lang, int variant = 0, int weight = 4, bool italic = false) { + bits = (weight & kWeightMask) | (italic ? kItalicMask : 0) + | (variant << kVariantShift) | (lang.bits() << kLangShift); + } int getWeight() const { return bits & kWeightMask; } bool getItalic() const { return (bits & kItalicMask) != 0; } + int getVariant() const { return (bits >> kVariantShift) & kVariantMask; } + FontLanguage getLanguage() const { return FontLanguage(bits >> kLangShift); } + bool operator==(const FontStyle other) const { return bits == other.bits; } - // TODO: language, variant hash_t hash() const { return bits; } private: - static const int kWeightMask = 0xf; - static const int kItalicMask = 16; + static const uint32_t kWeightMask = (1 << 4) - 1; + static const uint32_t kItalicMask = 1 << 4; + static const int kVariantShift = 5; + static const uint32_t kVariantMask = (1 << 2) - 1; + static const int kLangShift = 7; uint32_t bits; }; +enum FontVariant { + VARIANT_DEFAULT = 0, + VARIANT_COMPACT = 1, + VARIANT_ELEGANT = 2, +}; + inline hash_t hash_type(const FontStyle &style) { return style.hash(); } class FontFamily : public MinikinRefCounted { public: + FontFamily() { } + + FontFamily(FontLanguage lang, int variant) : mLang(lang), mVariant(variant) { + } + ~FontFamily(); // Add font to family, extracting style information from the font @@ -59,6 +109,9 @@ public: void addFont(MinikinFont* typeface, FontStyle style); MinikinFont* getClosestMatch(FontStyle style) const; + FontLanguage lang() const { return mLang; } + int variant() const { return mVariant; } + // API's for enumerating the fonts in a family. These don't guarantee any particular order size_t getNumFonts() const; MinikinFont* getFont(size_t index) const; @@ -73,6 +126,8 @@ private: MinikinFont* typeface; FontStyle style; }; + FontLanguage mLang; + int mVariant; std::vector<Font> mFonts; }; diff --git a/libs/minikin/CssParse.cpp b/libs/minikin/CssParse.cpp index 5bb6949..5e96007 100644 --- a/libs/minikin/CssParse.cpp +++ b/libs/minikin/CssParse.cpp @@ -20,6 +20,7 @@ #include <cstdio> // for sprintf - for debugging #include <minikin/CssParse.h> +#include <minikin/FontFamily.h> using std::map; using std::pair; @@ -27,27 +28,58 @@ using std::string; namespace android { -bool strEqC(const string str, size_t off, size_t len, const char* str2) { +static bool strEqC(const string str, size_t off, size_t len, const char* str2) { if (len != strlen(str2)) return false; return !memcmp(str.data() + off, str2, len); } -CssTag parseTag(const string str, size_t off, size_t len) { +static CssTag parseTag(const string str, size_t off, size_t len) { if (len == 0) return unknown; char c = str[off]; if (c == 'f') { if (strEqC(str, off, len, "font-size")) return fontSize; if (strEqC(str, off, len, "font-weight")) return fontWeight; if (strEqC(str, off, len, "font-style")) return fontStyle; + } else if (c == 'l') { + if (strEqC(str, off, len, "lang")) return cssLang; } else if (c == '-') { - if (strEqC(str, off, len, "-minikin-hinting")) return minikinHinting; if (strEqC(str, off, len, "-minikin-bidi")) return minikinBidi; + if (strEqC(str, off, len, "-minikin-hinting")) return minikinHinting; + if (strEqC(str, off, len, "-minikin-variant")) return minikinVariant; } return unknown; } -bool parseValue(const string str, size_t *off, size_t len, CssTag tag, - CssValue* v) { +static bool parseStringValue(const string& str, size_t* off, size_t len, CssTag tag, CssValue* v) { + const char* data = str.data(); + size_t beg = *off; + if (beg == len) return false; + char first = data[beg]; + bool quoted = false; + if (first == '\'' || first == '\"') { + quoted = true; + beg++; + } + size_t end; + for (end = beg; end < len; end++) { + char c = data[end]; + if (quoted && c == first) { + v->setStringValue(std::string(str, beg, end - beg)); + *off = end + 1; + return true; + } else if (!quoted && (c == ';' || c == ' ')) { + break; + } // TODO: deal with backslash escape, but only important for real strings + } + v->setStringValue(std::string(str, beg, end - beg)); + *off = end; + return true; +} + +static bool parseValue(const string& str, size_t* off, size_t len, CssTag tag, CssValue* v) { + if (tag == cssLang) { + return parseStringValue(str, off, len, tag, v); + } const char* data = str.data(); char* endptr; double fv = strtod(data + *off, &endptr); @@ -78,6 +110,12 @@ bool parseValue(const string str, size_t *off, size_t len, CssTag tag, } else { return false; } + } else if (tag == minikinVariant) { + if (strEqC(str, *off, taglen, "compact")) { + fv = VARIANT_COMPACT; + } else if (strEqC(str, *off, taglen, "elegant")) { + fv = VARIANT_ELEGANT; + } } else { return false; } @@ -91,10 +129,15 @@ string CssValue::toString(CssTag tag) const { if (mType == FLOAT) { if (tag == fontStyle) { return floatValue ? "italic" : "normal"; + } else if (tag == minikinVariant) { + if (floatValue == VARIANT_COMPACT) return "compact"; + if (floatValue == VARIANT_ELEGANT) return "elegant"; } char buf[64]; sprintf(buf, "%g", floatValue); return string(buf); + } else if (mType == STRING) { + return stringValue; // should probably quote } return ""; } diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp index e6ff117..89086ee 100644 --- a/libs/minikin/FontCollection.cpp +++ b/libs/minikin/FontCollection.cpp @@ -108,7 +108,19 @@ FontCollection::~FontCollection() { } } -const FontFamily* FontCollection::getFamilyForChar(uint32_t ch) const { +// Implement heuristic for choosing best-match font. Here are the rules: +// 1. If first font in the collection has the character, it wins. +// 2. If a font matches both language and script, it gets a score of 4. +// 3. If a font matches just language, it gets a score of 2. +// 4. Matching the "compact" or "elegant" variant adds one to the score. +// 5. Highest score wins, with ties resolved to the first font. + +// Note that we may want to make the selection more dependent on +// context, so for example a sequence of Devanagari, ZWJ, Devanagari +// would get itemized as one run, even though by the rules the ZWJ +// would go to the Latin font. +const FontFamily* FontCollection::getFamilyForChar(uint32_t ch, FontLanguage lang, + int variant) const { if (ch >= mMaxChar) { return NULL; } @@ -116,17 +128,33 @@ const FontFamily* FontCollection::getFamilyForChar(uint32_t ch) const { #ifdef VERBOSE_DEBUG ALOGD("querying range %d:%d\n", range.start, range.end); #endif + FontFamily* bestFamily = NULL; + int bestScore = -1; for (size_t i = range.start; i < range.end; i++) { const FontInstance* instance = mInstanceVec[i]; if (instance->mCoverage->get(ch)) { - return instance->mFamily; + FontFamily* family = instance->mFamily; + // First font family in collection always matches + if (mInstances[0].mFamily == family) { + return family; + } + int score = lang.match(family->lang()) * 2; + if (variant != 0 && variant == family->variant()) { + score++; + } + if (score > bestScore) { + bestScore = score; + bestFamily = family; + } } } - return NULL; + return bestFamily; } void FontCollection::itemize(const uint16_t *string, size_t string_size, FontStyle style, vector<Run>* result) const { + FontLanguage lang = style.getLanguage(); + int variant = style.getVariant(); const FontFamily* lastFamily = NULL; Run* run = NULL; int nShorts; @@ -140,7 +168,7 @@ void FontCollection::itemize(const uint16_t *string, size_t string_size, FontSty nShorts = 2; } } - const FontFamily* family = getFamilyForChar(ch); + const FontFamily* family = getFamilyForChar(ch, lang, variant); if (i == 0 || family != lastFamily) { Run dummy; result->push_back(dummy); @@ -149,6 +177,7 @@ void FontCollection::itemize(const uint16_t *string, size_t string_size, FontSty run->font = NULL; // maybe we should do something different here } else { run->font = family->getClosestMatch(style); + // TODO: simplify refcounting (FontCollection lifetime dominates) run->font->RefLocked(); } lastFamily = family; diff --git a/libs/minikin/FontFamily.cpp b/libs/minikin/FontFamily.cpp index d818f77..0fb98ae 100644 --- a/libs/minikin/FontFamily.cpp +++ b/libs/minikin/FontFamily.cpp @@ -30,6 +30,47 @@ 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 && buf[i] != '-' && buf[i] != '_'; i++) { + uint16_t c = buf[i]; + if (c == '-' || c == '_') break; + } + if (i == 2) { + bits = (uint8_t(buf[0]) << 8) | uint8_t(buf[1]); + } + 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 && 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; + } + } + // 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; +} + +int FontLanguage::match(const FontLanguage other) const { + int result = 0; + if ((mBits & kBaseLangMask) == (other.mBits & kBaseLangMask)) { + result++; + if ((mBits & kScriptMask) != 0 && (mBits & kScriptMask) == (other.mBits & kScriptMask)) { + result++; + } + } + return result; +} + FontFamily::~FontFamily() { for (size_t i = 0; i < mFonts.size(); i++) { mFonts[i].typeface->UnrefLocked(); diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp index aba8a1c..4028d9e 100644 --- a/libs/minikin/Layout.cpp +++ b/libs/minikin/Layout.cpp @@ -344,7 +344,16 @@ static FontStyle styleFromCss(const CssProperties &props) { if (props.hasTag(fontStyle)) { italic = props.value(fontStyle).getIntValue() != 0; } - return FontStyle(weight, italic); + FontLanguage lang; + if (props.hasTag(cssLang)) { + string langStr = props.value(cssLang).getStringValue(); + lang = FontLanguage(langStr.c_str(), langStr.size()); + } + int variant = 0; + if (props.hasTag(minikinVariant)) { + variant = props.value(minikinVariant).getIntValue(); + } + return FontStyle(lang, variant, weight, italic); } static hb_script_t codePointToScript(hb_codepoint_t codepoint) { @@ -486,7 +495,7 @@ static void clearHbFonts(LayoutContext* ctx) { // TODO: API should probably take context void Layout::doLayout(const uint16_t* buf, size_t start, size_t count, size_t bufSize, - const std::string& css) { + const string& css) { AutoMutex _l(gMinikinLock); LayoutContext ctx; @@ -599,7 +608,6 @@ void Layout::doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_ } appendLayout(value, bufStart); cache.mCache.put(key, value); - } void Layout::doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize, @@ -641,6 +649,10 @@ void Layout::doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t hb_buffer_reset(buffer); hb_buffer_set_script(buffer, script); hb_buffer_set_direction(buffer, isRtl? HB_DIRECTION_RTL : HB_DIRECTION_LTR); + if (ctx->props.hasTag(cssLang)) { + string lang = ctx->props.value(cssLang).getStringValue(); + hb_buffer_set_language(buffer, hb_language_from_string(lang.c_str(), -1)); + } hb_buffer_add_utf16(buffer, buf, bufSize, srunstart + start, srunend - srunstart); hb_shape(hbFont, buffer, NULL, 0); unsigned int numGlyphs; |