summaryrefslogtreecommitdiffstats
path: root/libs
diff options
context:
space:
mode:
authorRaph Levien <raph@google.com>2014-05-27 08:05:51 -0700
committerRaph Levien <raph@google.com>2014-05-29 15:16:32 -0700
commit7b221d97b7b64dc5ce457e19666d55d042e22e62 (patch)
tree5cc08f00c48aaa2b18921a8c4b2c5a9ded2f4e4a /libs
parent0297ee985c26b49fc2a3b0941db354c27f436bbf (diff)
downloadandroid_frameworks_minikin-7b221d97b7b64dc5ce457e19666d55d042e22e62.tar.gz
android_frameworks_minikin-7b221d97b7b64dc5ce457e19666d55d042e22e62.tar.bz2
android_frameworks_minikin-7b221d97b7b64dc5ce457e19666d55d042e22e62.zip
Language and variant selection
This patch adds a "lang" pseudo-CSS property and uses it both to select an appropriate font and control the "locl" OpenType feature to get the most appropriate rendering for the langauge and script. In addition, the "-minikin-variant" property selects between "compact" and "elegant" variants of a font, as the former is needed for vertically cramped spaces. This is part of the fix for bug 15179652 "Japanese font isn't shown on LMP". Change-Id: I7fab23c12d4c797a6d339a16e497b79a3afe9df1
Diffstat (limited to 'libs')
-rw-r--r--libs/minikin/CssParse.cpp53
-rw-r--r--libs/minikin/FontCollection.cpp37
-rw-r--r--libs/minikin/FontFamily.cpp41
-rw-r--r--libs/minikin/Layout.cpp18
4 files changed, 137 insertions, 12 deletions
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;