flutter_flutter/libs/minikin/FontCollection.cpp
Raph Levien 7b221d97b7 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
2014-05-29 15:16:32 -07:00

195 lines
6.8 KiB
C++

/*
* Copyright (C) 2013 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 VERBOSE_DEBUG
#define LOG_TAG "Minikin"
#include <cutils/log.h>
#include "MinikinInternal.h"
#include <minikin/CmapCoverage.h>
#include <minikin/FontCollection.h>
using std::vector;
namespace android {
template <typename T>
static inline T max(T a, T b) {
return a>b ? a : b;
}
uint32_t FontCollection::sNextId = 0;
FontCollection::FontCollection(const vector<FontFamily*>& typefaces) :
mMaxChar(0) {
AutoMutex _l(gMinikinLock);
mId = sNextId++;
vector<uint32_t> lastChar;
size_t nTypefaces = typefaces.size();
#ifdef VERBOSE_DEBUG
ALOGD("nTypefaces = %d\n", nTypefaces);
#endif
const FontStyle defaultStyle;
for (size_t i = 0; i < nTypefaces; i++) {
FontFamily* family = typefaces[i];
family->RefLocked();
FontInstance dummy;
mInstances.push_back(dummy); // emplace_back would be better
FontInstance* instance = &mInstances.back();
instance->mFamily = family;
instance->mCoverage = new SparseBitSet;
MinikinFont* typeface = family->getClosestMatch(defaultStyle);
if (typeface == NULL) {
ALOGE("FontCollection: closest match was null");
// TODO: we shouldn't hit this, as there should be more robust
// checks upstream to prevent empty/invalid FontFamily objects
continue;
}
#ifdef VERBOSE_DEBUG
ALOGD("closest match = %p, family size = %d\n", typeface, family->getNumFonts());
#endif
const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p');
size_t cmapSize = 0;
bool ok = typeface->GetTable(cmapTag, NULL, &cmapSize);
UniquePtr<uint8_t[]> cmapData(new uint8_t[cmapSize]);
ok = typeface->GetTable(cmapTag, cmapData.get(), &cmapSize);
CmapCoverage::getCoverage(*instance->mCoverage, cmapData.get(), cmapSize);
#ifdef VERBOSE_DEBUG
ALOGD("font coverage length=%d, first ch=%x\n", instance->mCoverage->length(),
instance->mCoverage->nextSetBit(0));
#endif
mMaxChar = max(mMaxChar, instance->mCoverage->length());
lastChar.push_back(instance->mCoverage->nextSetBit(0));
}
size_t nPages = (mMaxChar + kPageMask) >> kLogCharsPerPage;
size_t offset = 0;
for (size_t i = 0; i < nPages; i++) {
Range dummy;
mRanges.push_back(dummy);
Range* range = &mRanges.back();
#ifdef VERBOSE_DEBUG
ALOGD("i=%d: range start = %d\n", i, offset);
#endif
range->start = offset;
for (size_t j = 0; j < nTypefaces; j++) {
if (lastChar[j] < (i + 1) << kLogCharsPerPage) {
const FontInstance* instance = &mInstances[j];
mInstanceVec.push_back(instance);
offset++;
uint32_t nextChar = instance->mCoverage->nextSetBit((i + 1) << kLogCharsPerPage);
#ifdef VERBOSE_DEBUG
ALOGD("nextChar = %d (j = %d)\n", nextChar, j);
#endif
lastChar[j] = nextChar;
}
}
range->end = offset;
}
}
FontCollection::~FontCollection() {
for (size_t i = 0; i < mInstances.size(); i++) {
delete mInstances[i].mCoverage;
mInstances[i].mFamily->UnrefLocked();
}
}
// 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;
}
const Range& range = mRanges[ch >> kLogCharsPerPage];
#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)) {
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 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;
for (size_t i = 0; i < string_size; i += nShorts) {
nShorts = 1;
uint32_t ch = string[i];
// sigh, decode UTF-16 by hand here
if ((ch & 0xfc00) == 0xd800) {
if ((i + 1) < string_size) {
ch = 0x10000 + ((ch & 0x3ff) << 10) + (string[i + 1] & 0x3ff);
nShorts = 2;
}
}
const FontFamily* family = getFamilyForChar(ch, lang, variant);
if (i == 0 || family != lastFamily) {
Run dummy;
result->push_back(dummy);
run = &result->back();
if (family == NULL) {
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;
run->start = i;
}
run->end = i + nShorts;
}
}
uint32_t FontCollection::getId() const {
return mId;
}
} // namespace android