blob: 2e899296697e45ba2fe27e7e721eb29b53178dae [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/vr/font_fallback.h"
#include <map>
#include <set>
#include <string>
#include <vector>
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "chrome/browser/vr/ui_support.h"
#include "third_party/icu/source/common/unicode/uchar.h"
#include "third_party/icu/source/common/unicode/uscript.h"
#include "third_party/icu/source/common/unicode/utf16.h"
#include "third_party/skia/include/core/SkFontMgr.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_fallback.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/platform_font_skia.h"
namespace {
enum KnownGlyph {
UNDEFINED,
UNKNOWN,
KNOWN,
};
class CachedFont {
public:
static std::unique_ptr<CachedFont> CreateForTypeface(
sk_sp<SkTypeface> typeface) {
return base::WrapUnique<CachedFont>(new CachedFont(std::move(typeface)));
}
bool HasGlyphForCharacter(UChar32 character) {
// In order to increase cache hits, the cache is script based rather than
// character based. This also limits the cache size to the number of Unicode
// scripts (174 at the time of writing).
UErrorCode err = UErrorCode::U_ZERO_ERROR;
UScriptCode script = vr::UScriptGetScript(character, &err);
if (!U_SUCCESS(err) || script == UScriptCode::USCRIPT_INVALID_CODE)
return false;
auto& supported = supported_scripts_[script];
if (supported != UNDEFINED)
return supported == KNOWN;
uint16_t glyph_id = typeface_->unicharToGlyph(character);
supported = glyph_id ? KNOWN : UNKNOWN;
return supported == KNOWN;
}
std::string GetFontName() { return name_; }
private:
explicit CachedFont(sk_sp<SkTypeface> skia_face) {
SkString sk_name;
skia_face->getFamilyName(&sk_name);
name_ = std::string(sk_name.c_str(), sk_name.size());
typeface_ = std::move(skia_face);
}
sk_sp<SkTypeface> typeface_;
std::map<UScriptCode, KnownGlyph> supported_scripts_;
std::string name_;
DISALLOW_COPY_AND_ASSIGN(CachedFont);
};
using FontCache = std::map<SkFontID, std::unique_ptr<CachedFont>>;
base::LazyInstance<FontCache>::Leaky g_fonts = LAZY_INSTANCE_INITIALIZER;
class CachedFontSet {
public:
CachedFontSet() : locale_() {}
~CachedFontSet() = default;
void SetLocale(const std::string& locale) {
// Store font list for one locale at a time.
if (locale != locale_) {
font_ids_.clear();
unknown_chars_.clear();
locale_ = locale;
}
}
// Return a font name which provides a glyph for the Unicode code point
// specified by character.
// default_font: The main font for which fallbacks are required
// c: a UTF-32 code point
// preferred_locale: preferred locale identifier (if any) for |c|
// (e.g. "en", "ja", "zh-CN")
//
// The funtion, if it succeeds, sets |font_name|. Even if it succeeds, it may
// set |font_name| to the empty string if the character is supported by the
// default font.
//
// Returns:
// * false, if the request could not be satisfied or if the provided default
// font supports it.
// * true, otherwis.
//
bool GetFallbackFontNameForChar(UChar32 c, std::string* font_name) {
if (unknown_chars_.find(c) != unknown_chars_.end())
return false;
for (SkFontID font_id : font_ids_) {
std::unique_ptr<CachedFont>& font = g_fonts.Get()[font_id];
if (font->HasGlyphForCharacter(c)) {
*font_name = font->GetFontName();
return true;
}
}
sk_sp<SkFontMgr> font_mgr(SkFontMgr::RefDefault());
const char* bcp47_locales[] = {locale_.c_str()};
sk_sp<SkTypeface> tf(font_mgr->matchFamilyStyleCharacter(
nullptr, SkFontStyle(), locale_.empty() ? nullptr : bcp47_locales,
locale_.empty() ? 0 : 1, c));
if (tf) {
SkFontID font_id = tf->uniqueID();
font_ids_.push_back(font_id);
std::unique_ptr<CachedFont>& cached_font = g_fonts.Get()[font_id];
if (!cached_font)
cached_font = CachedFont::CreateForTypeface(tf);
*font_name = cached_font->GetFontName();
return true;
}
unknown_chars_.insert(c);
return false;
}
private:
std::string locale_;
std::vector<SkFontID> font_ids_;
std::set<UChar32> unknown_chars_;
DISALLOW_COPY_AND_ASSIGN(CachedFontSet);
};
base::LazyInstance<CachedFontSet>::Leaky g_cached_font_set =
LAZY_INSTANCE_INITIALIZER;
bool FontSupportsChar(const gfx::Font& font, UChar32 c) {
sk_sp<SkTypeface> typeface =
static_cast<gfx::PlatformFontSkia*>(font.platform_font())->typeface();
std::unique_ptr<CachedFont>& cached_font =
g_fonts.Get()[typeface->uniqueID()];
if (!cached_font)
cached_font = CachedFont::CreateForTypeface(std::move(typeface));
return cached_font->HasGlyphForCharacter(c);
}
} // namespace
namespace vr {
bool GetFallbackFontNameForChar(const gfx::Font& default_font,
UChar32 c,
const std::string& locale,
std::string* font_name) {
if (FontSupportsChar(default_font, c)) {
*font_name = std::string();
return true;
}
CachedFontSet& cached_font_set = g_cached_font_set.Get();
cached_font_set.SetLocale(locale);
return cached_font_set.GetFallbackFontNameForChar(c, font_name);
}
bool GetFontList(const std::string& preferred_font_name,
int font_size,
base::string16 text,
gfx::FontList* font_list,
bool validate_fonts_contain_all_code_points) {
gfx::Font preferred_font(preferred_font_name, font_size);
std::vector<gfx::Font> fonts{preferred_font};
std::set<std::string> names;
for (const UChar32 c : CollectDifferentChars(text)) {
std::string name;
if (!GetFallbackFontNameForChar(preferred_font, c, "", &name))
return false;
if (!name.empty())
names.insert(name);
}
for (const auto& name : names) {
fonts.push_back(gfx::Font(name, font_size));
}
*font_list = gfx::FontList(fonts);
return true;
}
} // namespace vr