Skip to content

Commit

Permalink
libtxt: support per-locale fallback font collections (flutter#5434)
Browse files Browse the repository at this point in the history
A given character may render differently in various locales, and fonts on the
host system may be tagged with locale metadata in order to support this.
If the text renderer needs to find a fallback font for a character, it should
pass the preferred locale to Skia's font manager which can use it to find the
best matching font.

The font collection also needs to maintain different caches of fallback fonts
for each locale.

See flutter/flutter#17879
  • Loading branch information
jason-simmons authored and najeira committed Jul 2, 2018
1 parent 1ed25ca commit 5a7ca95
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 59 deletions.
12 changes: 10 additions & 2 deletions third_party/txt/src/minikin/FontCollection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ const uint32_t TEXT_STYLE_VS = 0xFE0E;

uint32_t FontCollection::sNextId = 0;

// libtxt: return a locale string for a language list ID
std::string GetFontLocale(uint32_t langListId) {
const FontLanguages& langs = FontLanguageListCache::getById(langListId);
return langs.size() ? langs[0].getString() : "";
}

FontCollection::FontCollection(std::shared_ptr<FontFamily>&& typeface)
: mMaxChar(0) {
std::vector<std::shared_ptr<FontFamily>> typefaces;
Expand Down Expand Up @@ -295,7 +301,8 @@ const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(
// libtxt: check if the fallback font provider can match this character
if (mFallbackFontProvider) {
const std::shared_ptr<FontFamily>& fallback =
mFallbackFontProvider->matchFallbackFont(ch);
mFallbackFontProvider->matchFallbackFont(ch,
GetFontLocale(langListId));
if (fallback) {
return fallback;
}
Expand Down Expand Up @@ -332,7 +339,8 @@ const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(
// libtxt: check if the fallback font provider can match this character
if (mFallbackFontProvider) {
const std::shared_ptr<FontFamily>& fallback =
mFallbackFontProvider->matchFallbackFont(ch);
mFallbackFontProvider->matchFallbackFont(ch,
GetFontLocale(langListId));
if (fallback) {
return fallback;
}
Expand Down
3 changes: 2 additions & 1 deletion third_party/txt/src/minikin/FontCollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ class FontCollection {
public:
virtual ~FallbackFontProvider() = default;
virtual const std::shared_ptr<FontFamily>& matchFallbackFont(
uint32_t ch) = 0;
uint32_t ch,
std::string locale) = 0;
};

struct Run {
Expand Down
70 changes: 36 additions & 34 deletions third_party/txt/src/txt/font_collection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@

namespace txt {

namespace {

// 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",
};
bool FontCollection::FamilyKey::operator==(
const FontCollection::FamilyKey& other) const {
return font_family == other.font_family && locale == other.locale;
}

} // anonymous namespace
size_t FontCollection::FamilyKey::Hasher::operator()(
const FontCollection::FamilyKey& key) const {
return std::hash<std::string>()(key.font_family) ^
std::hash<std::string>()(key.locale);
}

class TxtFallbackFontProvider
: public minikin::FontCollection::FallbackFontProvider {
Expand All @@ -47,8 +48,9 @@ class TxtFallbackFontProvider
: font_collection_(font_collection) {}

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

private:
Expand Down Expand Up @@ -92,15 +94,19 @@ void FontCollection::DisableFontFallback() {
}

std::shared_ptr<minikin::FontCollection>
FontCollection::GetMinikinFontCollectionForFamily(const std::string& family) {
FontCollection::GetMinikinFontCollectionForFamily(
const std::string& font_family,
const std::string& locale) {
// Look inside the font collections cache first.
auto cached = font_collections_cache_.find(family);
FamilyKey family_key(font_family, locale);
auto cached = font_collections_cache_.find(family_key);
if (cached != font_collections_cache_.end()) {
return cached->second;
}

for (sk_sp<SkFontMgr>& manager : GetFontManagerOrder()) {
sk_sp<SkFontStyleSet> font_style_set(manager->matchFamily(family.c_str()));
sk_sp<SkFontStyleSet> font_style_set(
manager->matchFamily(font_family.c_str()));
if (font_style_set == nullptr || font_style_set->count() == 0) {
continue;
}
Expand Down Expand Up @@ -136,8 +142,8 @@ FontCollection::GetMinikinFontCollectionForFamily(const std::string& family) {
minikin_family,
};
if (enable_font_fallback_) {
for (const auto& fallback : fallback_fonts_)
minikin_families.push_back(fallback.second);
for (SkFontID font_id : fallback_fonts_for_locale_[locale])
minikin_families.push_back(fallback_fonts_[font_id]);
}

// Create the minikin font collection.
Expand All @@ -149,16 +155,16 @@ FontCollection::GetMinikinFontCollectionForFamily(const std::string& family) {
}

// Cache the font collection for future queries.
font_collections_cache_[family] = font_collection;
font_collections_cache_[family_key] = font_collection;

return font_collection;
}

const auto default_font_family = GetDefaultFontFamily();
if (family != default_font_family) {
if (font_family != default_font_family) {
std::shared_ptr<minikin::FontCollection> default_collection =
GetMinikinFontCollectionForFamily(default_font_family);
font_collections_cache_[family] = default_collection;
GetMinikinFontCollectionForFamily(default_font_family, "");
font_collections_cache_[family_key] = default_collection;
return default_collection;
}

Expand All @@ -167,21 +173,27 @@ FontCollection::GetMinikinFontCollectionForFamily(const std::string& family) {
}

const std::shared_ptr<minikin::FontFamily>& FontCollection::MatchFallbackFont(
uint32_t ch) {
uint32_t ch,
std::string locale) {
for (const sk_sp<SkFontMgr>& manager : GetFontManagerOrder()) {
sk_sp<SkTypeface> typeface(
manager->matchFamilyStyleCharacter(0, SkFontStyle(), nullptr, 0, ch));
std::vector<const char*> bcp47;
if (!locale.empty())
bcp47.push_back(locale.c_str());
sk_sp<SkTypeface> typeface(manager->matchFamilyStyleCharacter(
0, SkFontStyle(), bcp47.data(), bcp47.size(), ch));
if (!typeface)
continue;

return GetFontFamilyForTypeface(typeface);
fallback_fonts_for_locale_[locale].insert(typeface->uniqueID());

return GetFallbackFont(typeface);
}

return null_family_;
}

const std::shared_ptr<minikin::FontFamily>&
FontCollection::GetFontFamilyForTypeface(const sk_sp<SkTypeface>& typeface) {
FontCollection::GetFallbackFont(const sk_sp<SkTypeface>& typeface) {
SkFontID typeface_id = typeface->uniqueID();
auto fallback_it = fallback_fonts_.find(typeface_id);
if (fallback_it != fallback_fonts_.end()) {
Expand All @@ -202,14 +214,4 @@ FontCollection::GetFontFamilyForTypeface(const sk_sp<SkTypeface>& typeface) {
return insert_it.first->second;
}

void FontCollection::UpdateFallbackFonts(sk_sp<SkFontMgr> manager) {
for (const std::string& family : last_resort_fonts) {
sk_sp<SkTypeface> typeface(
manager->matchFamilyStyle(family.c_str(), SkFontStyle()));
if (typeface) {
GetFontFamilyForTypeface(typeface);
}
}
}

} // namespace txt
34 changes: 27 additions & 7 deletions third_party/txt/src/txt/font_collection.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
#ifndef LIB_TXT_SRC_FONT_COLLECTION_H_
#define LIB_TXT_SRC_FONT_COLLECTION_H_

#include <deque>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include "lib/fxl/macros.h"
Expand All @@ -28,6 +28,7 @@
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/ports/SkFontMgr.h"
#include "txt/asset_font_manager.h"
#include "txt/text_style.h"

namespace txt {

Expand All @@ -44,32 +45,51 @@ class FontCollection : public std::enable_shared_from_this<FontCollection> {
void SetTestFontManager(sk_sp<SkFontMgr> font_manager);

std::shared_ptr<minikin::FontCollection> GetMinikinFontCollectionForFamily(
const std::string& family);
const std::string& family,
const std::string& locale);

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

// Do not provide alternative fonts that can match characters which are
// missing from the requested font family.
void DisableFontFallback();

private:
struct FamilyKey {
FamilyKey(const std::string& family, const std::string& loc)
: font_family(family), locale(loc) {}

std::string font_family;
std::string locale;

bool operator==(const FamilyKey& other) const;

struct Hasher {
size_t operator()(const FamilyKey& key) const;
};
};

sk_sp<SkFontMgr> default_font_manager_;
sk_sp<SkFontMgr> asset_font_manager_;
sk_sp<SkFontMgr> test_font_manager_;
std::unordered_map<std::string, std::shared_ptr<minikin::FontCollection>>
std::unordered_map<FamilyKey,
std::shared_ptr<minikin::FontCollection>,
FamilyKey::Hasher>
font_collections_cache_;
std::unordered_map<SkFontID, std::shared_ptr<minikin::FontFamily>>
fallback_fonts_;
std::unordered_map<std::string, std::set<SkFontID>>
fallback_fonts_for_locale_;
std::shared_ptr<minikin::FontFamily> null_family_;
bool enable_font_fallback_;

std::vector<sk_sp<SkFontMgr>> GetFontManagerOrder() const;

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

void UpdateFallbackFonts(sk_sp<SkFontMgr> manager);

FXL_DISALLOW_COPY_AND_ASSIGN(FontCollection);
};

Expand Down
43 changes: 28 additions & 15 deletions third_party/txt/src/txt/paragraph.cc
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,6 @@ void GetFontAndMinikinPaint(const TextStyle& style,
paint->paintFlags |= minikin::LinearTextFlag;
}

sk_sp<SkTypeface> GetDefaultSkiaTypeface(
const std::shared_ptr<txt::FontCollection>& font_collection,
const TextStyle& style) {
std::shared_ptr<minikin::FontCollection> collection =
font_collection->GetMinikinFontCollectionForFamily(style.font_family);
minikin::FakedFont faked_font =
collection->baseFontFaked(GetMinikinFontStyle(style));
return static_cast<FontSkia*>(faked_font.font)->GetSkTypeface();
}

void FindWords(const std::vector<uint16_t>& text,
size_t start,
size_t end,
Expand Down Expand Up @@ -286,8 +276,7 @@ bool Paragraph::ComputeLineBreaks() {
minikin::MinikinPaint paint;
GetFontAndMinikinPaint(run.style, &font, &paint);
std::shared_ptr<minikin::FontCollection> collection =
font_collection_->GetMinikinFontCollectionForFamily(
run.style.font_family);
GetMinikinFontCollectionForStyle(run.style);
if (collection == nullptr) {
FXL_LOG(INFO) << "Could not find font collection for family \""
<< run.style.font_family << "\".";
Expand Down Expand Up @@ -502,8 +491,7 @@ void Paragraph::Layout(double width, bool force) {
paint.setTextSize(run.style().font_size);

std::shared_ptr<minikin::FontCollection> minikin_font_collection =
font_collection_->GetMinikinFontCollectionForFamily(
run.style().font_family);
GetMinikinFontCollectionForStyle(run.style());

// Lay out this run.
uint16_t* text_ptr = text_.data();
Expand Down Expand Up @@ -753,7 +741,7 @@ void Paragraph::Layout(double width, bool force) {
if (paint_records.empty()) {
SkPaint::FontMetrics metrics;
TextStyle style(paragraph_style_.GetTextStyle());
paint.setTypeface(GetDefaultSkiaTypeface(font_collection_, style));
paint.setTypeface(GetDefaultSkiaTypeface(style));
paint.setTextSize(style.font_size);
paint.getFontMetrics(&metrics);
update_line_metrics(metrics, style);
Expand Down Expand Up @@ -855,6 +843,31 @@ void Paragraph::SetFontCollection(
font_collection_ = std::move(font_collection);
}

std::shared_ptr<minikin::FontCollection>
Paragraph::GetMinikinFontCollectionForStyle(const TextStyle& style) {
std::string locale;
if (!style.locale.empty()) {
uint32_t language_list_id =
minikin::FontStyle::registerLanguageList(style.locale);
const minikin::FontLanguages& langs =
minikin::FontLanguageListCache::getById(language_list_id);
if (langs.size()) {
locale = langs[0].getString();
}
}

return font_collection_->GetMinikinFontCollectionForFamily(style.font_family,
locale);
}

sk_sp<SkTypeface> Paragraph::GetDefaultSkiaTypeface(const TextStyle& style) {
std::shared_ptr<minikin::FontCollection> collection =
GetMinikinFontCollectionForStyle(style);
minikin::FakedFont faked_font =
collection->baseFontFaked(GetMinikinFontStyle(style));
return static_cast<FontSkia*>(faked_font.font)->GetSkTypeface();
}

// The x,y coordinates will be the very top left corner of the rendered
// paragraph.
void Paragraph::Paint(SkCanvas* canvas, double x, double y) {
Expand Down
7 changes: 7 additions & 0 deletions third_party/txt/src/txt/paragraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,13 @@ class Paragraph {
// Draws the background onto the canvas.
void PaintBackground(SkCanvas* canvas, const PaintRecord& record);

// Obtain a Minikin font collection matching this text style.
std::shared_ptr<minikin::FontCollection> GetMinikinFontCollectionForStyle(
const TextStyle& style);

// Get a default SkTypeface for a text style.
sk_sp<SkTypeface> GetDefaultSkiaTypeface(const TextStyle& style);

FXL_DISALLOW_COPY_AND_ASSIGN(Paragraph);
};

Expand Down

0 comments on commit 5a7ca95

Please sign in to comment.