Skip to content

Commit

Permalink
libtxt: extend Minikin to find a fallback font if its font collection…
Browse files Browse the repository at this point in the history
… can not match a given character (flutter#4595)

Minikin layout uses a FontCollection containing a list of pre-selected fonts
for a particular font family.  This patch extends the FontCollection to invoke
a hook provided by libtxt if layout sees a character that can not be rendered
by any font in the collection.

* Change the Minikin lock to a recursive mutex.  This is required because the
  fallback font provider may create new fonts during a layout operation that
  already holds the lock.
* Implement a fallback font provider hook that queries Skia for fonts matching
  an unrecognized character.
* Maintain a cache of fallback fonts.  Prepopulate the cache with fonts
  covering some commonly used character classes.
* Add a last resort font list for cases where Skia's font manager can not
  find any font for a character (similar to Blink's FontCache::getLastResortFallbackFont)
  • Loading branch information
jason-simmons authored Jan 29, 2018
1 parent 0de8218 commit 9a40224
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 34 deletions.
13 changes: 11 additions & 2 deletions third_party/txt/src/minikin/FontCollection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ FontCollection::FontCollection(

void FontCollection::init(
const vector<std::shared_ptr<FontFamily>>& typefaces) {
std::lock_guard<std::mutex> _l(gMinikinLock);
std::lock_guard<std::recursive_mutex> _l(gMinikinLock);
mId = sNextId++;
vector<uint32_t> lastChar;
size_t nTypefaces = typefaces.size();
Expand Down Expand Up @@ -321,6 +321,15 @@ const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(
}
}
if (bestFamilyIndex == -1) {
// libtxt: check if the fallback font provider can match this character
if (mFallbackFontProvider) {
const std::shared_ptr<FontFamily>& fallback =
mFallbackFontProvider->matchFallbackFont(ch);
if (fallback) {
return fallback;
}
}

UErrorCode errorCode = U_ZERO_ERROR;
const UNormalizer2* normalizer = unorm2_getNFDInstance(&errorCode);
if (U_SUCCESS(errorCode)) {
Expand Down Expand Up @@ -380,7 +389,7 @@ bool FontCollection::hasVariationSelector(uint32_t baseCodepoint,
return false;
}

std::lock_guard<std::mutex> _l(gMinikinLock);
std::lock_guard<std::recursive_mutex> _l(gMinikinLock);

// Currently mRanges can not be used here since it isn't aware of the
// variation sequence.
Expand Down
15 changes: 15 additions & 0 deletions third_party/txt/src/minikin/FontCollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ class FontCollection {
const std::vector<std::shared_ptr<FontFamily>>& typefaces);
explicit FontCollection(std::shared_ptr<FontFamily>&& typeface);

// libtxt extension: an interface for looking up fallback fonts for characters
// that do not match this collection's font families.
class FallbackFontProvider {
public:
virtual const std::shared_ptr<FontFamily>& matchFallbackFont(
uint32_t ch) = 0;
};

struct Run {
FakedFont fakedFont;
int start;
Expand Down Expand Up @@ -63,6 +71,10 @@ class FontCollection {

uint32_t getId() const;

void set_fallback_font_provider(std::unique_ptr<FallbackFontProvider> ffp) {
mFallbackFontProvider = std::move(ffp);
}

private:
static const int kLogCharsPerPage = 8;
static const int kPageMask = (1 << kLogCharsPerPage) - 1;
Expand Down Expand Up @@ -131,6 +143,9 @@ class FontCollection {

// Set of supported axes in this collection.
std::unordered_set<AxisTag> mSupportedAxes;

// libtxt extension: Fallback font provider.
std::unique_ptr<FallbackFontProvider> mFallbackFontProvider;
};

} // namespace minikin
Expand Down
8 changes: 4 additions & 4 deletions third_party/txt/src/minikin/FontFamily.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ android::hash_t FontStyle::hash() const {

// static
uint32_t FontStyle::registerLanguageList(const std::string& languages) {
std::lock_guard<std::mutex> _l(gMinikinLock);
std::lock_guard<std::recursive_mutex> _l(gMinikinLock);
return FontLanguageListCache::getId(languages);
}

Expand Down Expand Up @@ -115,7 +115,7 @@ FontFamily::FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts)
bool FontFamily::analyzeStyle(const std::shared_ptr<MinikinFont>& typeface,
int* weight,
bool* italic) {
std::lock_guard<std::mutex> _l(gMinikinLock);
std::lock_guard<std::recursive_mutex> _l(gMinikinLock);
const uint32_t os2Tag = MinikinFont::MakeTag('O', 'S', '/', '2');
HbBlob os2Table(getFontTable(typeface.get(), os2Tag));
if (os2Table.get() == nullptr)
Expand Down Expand Up @@ -175,7 +175,7 @@ bool FontFamily::isColorEmojiFamily() const {
}

void FontFamily::computeCoverage() {
std::lock_guard<std::mutex> _l(gMinikinLock);
std::lock_guard<std::recursive_mutex> _l(gMinikinLock);
const FontStyle defaultStyle;
const MinikinFont* typeface = getClosestMatch(defaultStyle).font;
const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p');
Expand Down Expand Up @@ -233,7 +233,7 @@ std::shared_ptr<FontFamily> FontFamily::createFamilyWithVariation(
std::vector<Font> fonts;
for (const Font& font : mFonts) {
bool supportedVariations = false;
std::lock_guard<std::mutex> _l(gMinikinLock);
std::lock_guard<std::recursive_mutex> _l(gMinikinLock);
std::unordered_set<AxisTag> supportedAxes = font.getSupportedAxesLocked();
if (!supportedAxes.empty()) {
for (const FontVariation& variation : variations) {
Expand Down
6 changes: 3 additions & 3 deletions third_party/txt/src/minikin/Layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ void Layout::doLayout(const uint16_t* buf,
const FontStyle& style,
const MinikinPaint& paint,
const std::shared_ptr<FontCollection>& collection) {
std::lock_guard<std::mutex> _l(gMinikinLock);
std::lock_guard<std::recursive_mutex> _l(gMinikinLock);

LayoutContext ctx;
ctx.style = style;
Expand All @@ -628,7 +628,7 @@ float Layout::measureText(const uint16_t* buf,
const MinikinPaint& paint,
const std::shared_ptr<FontCollection>& collection,
float* advances) {
std::lock_guard<std::mutex> _l(gMinikinLock);
std::lock_guard<std::recursive_mutex> _l(gMinikinLock);

LayoutContext ctx;
ctx.style = style;
Expand Down Expand Up @@ -1197,7 +1197,7 @@ void Layout::getBounds(MinikinRect* bounds) const {
}

void Layout::purgeCaches() {
std::lock_guard<std::mutex> _l(gMinikinLock);
std::lock_guard<std::recursive_mutex> _l(gMinikinLock);
LayoutCache& layoutCache = LayoutEngine::getInstance().layoutCache;
layoutCache.clear();
purgeHbFontCacheLocked();
Expand Down
2 changes: 1 addition & 1 deletion third_party/txt/src/minikin/MinikinFont.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
namespace minikin {

MinikinFont::~MinikinFont() {
std::lock_guard<std::mutex> _l(gMinikinLock);
std::lock_guard<std::recursive_mutex> _l(gMinikinLock);
purgeHbFontLocked(this);
}

Expand Down
2 changes: 1 addition & 1 deletion third_party/txt/src/minikin/MinikinInternal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

namespace minikin {

std::mutex gMinikinLock;
std::recursive_mutex gMinikinLock;

void assertMinikinLocked() {
#ifdef ENABLE_RACE_DETECTION
Expand Down
2 changes: 1 addition & 1 deletion third_party/txt/src/minikin/MinikinInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ namespace minikin {
// Presently, that's implemented by through a global lock, and having
// all external interfaces take that lock.

extern std::mutex gMinikinLock;
extern std::recursive_mutex gMinikinLock;

// Aborts if gMinikinLock is not acquired. Do nothing on the release build.
void assertMinikinLocked();
Expand Down
96 changes: 76 additions & 20 deletions third_party/txt/src/txt/font_collection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,29 @@ const std::vector<SkUnichar> fallback_characters{
0x627, // Arabic
};

// Font families that will be used as a last resort if no font manager provides
// a font matching a particular character.
const std::vector<std::string> last_resort_fonts{
"Arial",
};

} // anonymous namespace

class TxtFallbackFontProvider
: public minikin::FontCollection::FallbackFontProvider {
public:
TxtFallbackFontProvider(std::shared_ptr<FontCollection> font_collection)
: font_collection_(font_collection) {}

virtual const std::shared_ptr<minikin::FontFamily>& matchFallbackFont(
uint32_t ch) {
return font_collection_->MatchFallbackFont(ch);
}

private:
std::shared_ptr<FontCollection> font_collection_;
};

FontCollection::FontCollection() = default;

FontCollection::~FontCollection() = default;
Expand Down Expand Up @@ -117,6 +138,8 @@ FontCollection::GetMinikinFontCollectionForFamily(const std::string& family) {
// Create the minikin font collection.
auto font_collection =
std::make_shared<minikin::FontCollection>(std::move(minikin_families));
font_collection->set_fallback_font_provider(
std::make_unique<TxtFallbackFontProvider>(shared_from_this()));

// Cache the font collection for future queries.
font_collections_cache_[family] = font_collection;
Expand All @@ -133,29 +156,62 @@ FontCollection::GetMinikinFontCollectionForFamily(const std::string& family) {
return nullptr;
}

void FontCollection::UpdateFallbackFonts(sk_sp<SkFontMgr> manager) {
char language_tag[ULOC_FULLNAME_CAPACITY];
UErrorCode uerr;
uloc_toLanguageTag(icu::Locale::getDefault().getName(), language_tag,
ULOC_FULLNAME_CAPACITY, FALSE, &uerr);
if (U_FAILURE(uerr))
return;
const char* bcp47[] = {language_tag};

for (SkUnichar fallback_char : fallback_characters) {
if (fallback_fonts_.count(fallback_char))
const std::shared_ptr<minikin::FontFamily>& FontCollection::MatchFallbackFont(
uint32_t ch) {
for (const auto& manager : skia_font_managers_) {
sk_sp<SkTypeface> typeface(
manager->matchFamilyStyleCharacter(0, SkFontStyle(), nullptr, 0, ch));
if (!typeface)
continue;

sk_sp<SkTypeface> skia_typeface(manager->matchFamilyStyleCharacter(
0, SkFontStyle(), bcp47, 1, fallback_char));
if (!skia_typeface)
continue;
return GetFontFamilyForTypeface(typeface);
}

std::vector<minikin::Font> minikin_fonts;
minikin_fonts.emplace_back(std::make_shared<FontSkia>(skia_typeface),
minikin::FontStyle());
fallback_fonts_[fallback_char] =
std::make_shared<minikin::FontFamily>(std::move(minikin_fonts));
return null_family_;
}

const std::shared_ptr<minikin::FontFamily>&
FontCollection::GetFontFamilyForTypeface(const sk_sp<SkTypeface>& typeface) {
SkFontID typeface_id = typeface->uniqueID();
auto fallback_it = fallback_fonts_.find(typeface_id);
if (fallback_it != fallback_fonts_.end()) {
return fallback_it->second;
}

std::vector<minikin::Font> minikin_fonts;
minikin_fonts.emplace_back(std::make_shared<FontSkia>(typeface),
minikin::FontStyle());
auto insert_it = fallback_fonts_.insert(std::make_pair(
typeface_id,
std::make_shared<minikin::FontFamily>(std::move(minikin_fonts))));

// Clear the cache to force creation of new font collections that will include
// this fallback font.
font_collections_cache_.clear();

return insert_it.first->second;
}

void FontCollection::UpdateFallbackFonts(sk_sp<SkFontMgr> manager) {
// Prepopulate the fallback font cache with fonts matching some widely
// used character classes.
for (SkUnichar fallback_char : fallback_characters) {
sk_sp<SkTypeface> typeface(manager->matchFamilyStyleCharacter(
0, SkFontStyle(), nullptr, 0, fallback_char));
if (typeface) {
// Create a Minikin font family for this typeface if one does not already
// exist.
GetFontFamilyForTypeface(typeface);
}
}

// Add additional font families to be used if nothing else matches.
for (const std::string& family : last_resort_fonts) {
sk_sp<SkTypeface> typeface(
manager->matchFamilyStyle(family.c_str(), SkFontStyle()));
if (typeface) {
GetFontFamilyForTypeface(typeface);
}
}
}

Expand Down
10 changes: 8 additions & 2 deletions third_party/txt/src/txt/font_collection.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

namespace txt {

class FontCollection {
class FontCollection : public std::enable_shared_from_this<FontCollection> {
public:
FontCollection();

Expand All @@ -46,12 +46,18 @@ class FontCollection {
std::shared_ptr<minikin::FontCollection> GetMinikinFontCollectionForFamily(
const std::string& family);

const std::shared_ptr<minikin::FontFamily>& MatchFallbackFont(uint32_t ch);

private:
std::deque<sk_sp<SkFontMgr>> skia_font_managers_;
std::unordered_map<std::string, std::shared_ptr<minikin::FontCollection>>
font_collections_cache_;
std::unordered_map<SkUnichar, std::shared_ptr<minikin::FontFamily>>
std::unordered_map<SkFontID, std::shared_ptr<minikin::FontFamily>>
fallback_fonts_;
std::shared_ptr<minikin::FontFamily> null_family_;

const std::shared_ptr<minikin::FontFamily>& GetFontFamilyForTypeface(
const sk_sp<SkTypeface>& typeface);

void UpdateFallbackFonts(sk_sp<SkFontMgr> manager);

Expand Down

0 comments on commit 9a40224

Please sign in to comment.