Commit 587827f0 authored by Sergey Ulanov's avatar Sergey Ulanov Committed by Commit Bot

[Fuchsia] Add SkFontMgr implementation for Fuchsia

Initial implementation of SkFontMgr for Fuchsia that implements minimal
SkFontMgr functionality used in Blink. There are still a number of
features that don't work properly and will require changes in the
system FontProvider to get fixed:
 1. Character to font matching (see onMatchFamilyStyleCharacter()).
    Currently the system API doesn't provide this feature.
 2. Style matching according to CSS3: Font style matching rules
    implemented in Fuchsia's font provider do not match CSS3 rules.
 3. Font aliasing: FontProvider supports only 3 Roboto fonts (Roboto,
    RobotoSlab and RobotoMono) doesn't support any font family aliases.
    To workaround this issue aliasing implemented directly in
    FuchsiaFontManager.

The new SkFontMgr is not enabled yet because it doesn't work in sandbox.
Will enabled it later in a separate CL.

Bug: 800156
Change-Id: I98a9bed3f98c492577cc9cf0a81f190c8837da31
Reviewed-on: https://chromium-review.googlesource.com/1114192
Commit-Queue: Sergey Ulanov <sergeyu@chromium.org>
Reviewed-by: default avatarWez <wez@chromium.org>
Reviewed-by: default avatarNico Weber <thakis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#572360}
parent 2aa1ee72
......@@ -481,6 +481,13 @@ component("skia") {
"//third_party/skia/src/ports/SkFontMgr_custom.cpp",
"//third_party/skia/src/ports/SkFontMgr_custom_empty.cpp",
"ext/fontmgr_default_fuchsia.cc",
"ext/fontmgr_default_fuchsia.h",
"ext/fontmgr_fuchsia.cc",
"ext/fontmgr_fuchsia.h",
]
deps += [
"//third_party/fuchsia-sdk:fonts",
"//third_party/icu:icuuc",
]
}
......@@ -795,6 +802,17 @@ test("skia_unittests") {
"//skia/public/interfaces:test_interfaces",
]
}
if (is_fuchsia) {
sources += [ "ext/fontmgr_fuchsia_unittest.cc" ]
deps += [
"//third_party/fuchsia-sdk:fonts",
"//third_party/test_fonts",
]
data_deps = [
"//third_party/test_fonts",
]
}
}
if (!is_ios) {
......
......@@ -22,5 +22,7 @@ SK_API sk_sp<SkFontMgr> SkFontMgr::Factory() {
if (g_default_fontmgr) {
return sk_ref_sp(g_default_fontmgr);
}
// TODO(crbug.com/800156): Replace with FuchsiaFontManager when we can
// access FontProvider in sandbox.
return SkFontMgr_New_Custom_Empty();
}
// 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 "skia/ext/fontmgr_fuchsia.h"
#include <lib/zx/vmar.h>
#include <unordered_map>
#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/small_map.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_checker.h"
#include "third_party/icu/source/common/unicode/uchar.h"
#include "third_party/skia/src/core/SkFontDescriptor.h"
#include "third_party/skia/src/core/SkMakeUnique.h"
#include "third_party/skia/src/ports/SkFontHost_FreeType_common.h"
#include "third_party/skia/src/ports/SkFontMgr_custom.h"
namespace skia {
namespace {
constexpr char kDefaultFont[] = "Roboto";
// Currently FontProvider doesn't support font aliases. The map below is used to
// map common web fonts to font families that are expected to be present in
// FontProvider.
constexpr struct {
const char* font_name_in;
const char* font_name_out;
} kFontMap[] = {{"sans", "Roboto"},
{"sans-serif", "Roboto"},
{"arial", "Roboto"},
{"helvetica", "Roboto"},
{"roboto", "Roboto"},
{"roboto slab", "RobotoSlab"},
{"serif", "RobotoSlab"},
{"georgia", "RobotoSlab"},
{"times", "RobotoSlab"},
{"times roman", "RobotoSlab"},
{"times new roman", "RobotoSlab"},
{"roboto mono", "RobotoMono"},
{"consolas", "RobotoMono"},
{"courier", "RobotoMono"},
{"courier new", "RobotoMono"},
{"monospace", "RobotoMono"}};
fuchsia::fonts::FontSlant ToFontSlant(SkFontStyle::Slant slant) {
return (slant == SkFontStyle::kItalic_Slant)
? fuchsia::fonts::FontSlant::ITALIC
: fuchsia::fonts::FontSlant::UPRIGHT;
}
void UnmapMemory(const void* buffer, void* context) {
const uintptr_t size = reinterpret_cast<uintptr_t>(context);
zx::vmar::root_self().unmap(reinterpret_cast<uintptr_t>(buffer), size);
}
sk_sp<SkData> BufferToSkData(fuchsia::mem::Buffer data) {
uintptr_t buffer = 0;
zx_status_t status = zx::vmar::root_self().map(0, data.vmo, 0, data.size,
ZX_VM_FLAG_PERM_READ, &buffer);
if (status != ZX_OK) {
ZX_DLOG(ERROR, status) << "zx_vmar_map";
return nullptr;
}
return SkData::MakeWithProc(reinterpret_cast<void*>(buffer), data.size,
UnmapMemory, reinterpret_cast<void*>(data.size));
}
// SkTypeface that notifies FontCache when it is being destroyed.
class CachedTypeface : public SkTypeface_Stream {
public:
CachedTypeface(std::unique_ptr<SkFontData> font_data,
const SkFontStyle& style,
bool is_fixed_pitch,
const SkString family_name,
base::OnceClosure on_deleted)
: SkTypeface_Stream(std::move(font_data),
style,
is_fixed_pitch,
/*sys_font=*/true,
family_name),
on_deleted_(std::move(on_deleted)) {}
~CachedTypeface() override {
if (on_deleted_)
std::move(on_deleted_).Run();
}
private:
base::OnceClosure on_deleted_;
DISALLOW_COPY_AND_ASSIGN(CachedTypeface);
};
sk_sp<SkTypeface> CreateTypefaceFromSkStream(
std::unique_ptr<SkStreamAsset> stream,
const SkFontArguments& args,
base::OnceClosure on_deleted) {
using Scanner = SkTypeface_FreeType::Scanner;
Scanner scanner;
bool is_fixed_pitch;
SkFontStyle style;
SkString name;
Scanner::AxisDefinitions axis_definitions;
if (!scanner.scanFont(stream.get(), args.getCollectionIndex(), &name, &style,
&is_fixed_pitch, &axis_definitions)) {
return nullptr;
}
const SkFontArguments::VariationPosition position =
args.getVariationDesignPosition();
SkAutoSTMalloc<4, SkFixed> axis_values(axis_definitions.count());
Scanner::computeAxisValues(axis_definitions, position, axis_values, name);
auto font_data =
std::make_unique<SkFontData>(std::move(stream), args.getCollectionIndex(),
axis_values.get(), axis_definitions.count());
return sk_make_sp<CachedTypeface>(std::move(font_data), style, is_fixed_pitch,
name, std::move(on_deleted));
}
sk_sp<SkTypeface> CreateTypefaceFromFontData(fuchsia::fonts::FontData font_data,
base::OnceClosure on_deleted) {
sk_sp<SkData> data = BufferToSkData(std::move(font_data.buffer));
if (!data)
return nullptr;
// TODO(https://crbug.com/800156): Initialize font arguments with font index
// when font collection support is implemented in FontProvider.
SkFontArguments args;
return CreateTypefaceFromSkStream(
std::make_unique<SkMemoryStream>(std::move(data)), args,
std::move(on_deleted));
}
} // namespace
class FuchsiaFontManager::FontCache {
public:
FontCache();
~FontCache();
sk_sp<SkTypeface> GetTypefaceFromFontData(fuchsia::fonts::FontData font_data);
private:
void OnTypefaceDeleted(zx_koid_t vmo_koid);
THREAD_CHECKER(thread_checker_);
// SkTypeface cache. They key is koid of the VMO that contains the typeface.
// This allows to reuse previously-created SkTypeface when FontProvider
// returns FontData with the same VMO.
base::small_map<std::unordered_map<zx_koid_t, SkTypeface*>> typefaces_;
base::WeakPtrFactory<FontCache> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(FontCache);
};
FuchsiaFontManager::FontCache::FontCache() : weak_factory_(this) {}
FuchsiaFontManager::FontCache::~FontCache() = default;
sk_sp<SkTypeface> FuchsiaFontManager::FontCache::GetTypefaceFromFontData(
fuchsia::fonts::FontData font_data) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
zx_info_handle_basic_t vmo_info;
zx_status_t status = font_data.buffer.vmo.get_info(
ZX_INFO_HANDLE_BASIC, &vmo_info, sizeof(vmo_info), nullptr, nullptr);
if (status != ZX_OK) {
ZX_DLOG(ERROR, status) << "zx_object_get_info";
return nullptr;
}
sk_sp<SkTypeface> result;
SkTypeface** cached_typeface = &(typefaces_[vmo_info.koid]);
if (*cached_typeface) {
result = sk_ref_sp(*cached_typeface);
} else {
result = CreateTypefaceFromFontData(
std::move(font_data),
base::BindOnce(&FontCache::OnTypefaceDeleted,
weak_factory_.GetWeakPtr(), vmo_info.koid));
*cached_typeface = result.get();
}
return result;
}
void FuchsiaFontManager::FontCache::OnTypefaceDeleted(zx_koid_t vmo_koid) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
bool was_found = typefaces_.erase(vmo_koid) != 0;
DCHECK(was_found);
}
FuchsiaFontManager::FuchsiaFontManager(
fuchsia::fonts::FontProviderSync2Ptr font_provider)
: font_provider_(std::move(font_provider)), font_cache_(new FontCache()) {
for (auto& m : kFontMap) {
font_map_[m.font_name_in] = m.font_name_out;
}
// Get default font.
default_typeface_.reset(onMatchFamilyStyle(kDefaultFont, SkFontStyle()));
if (!default_typeface_)
LOG(FATAL) << "Failed to get default font from the FontProvider.";
}
FuchsiaFontManager::~FuchsiaFontManager() = default;
int FuchsiaFontManager::onCountFamilies() const {
NOTREACHED();
return 0;
}
void FuchsiaFontManager::onGetFamilyName(int index,
SkString* family_name) const {
NOTREACHED();
}
SkFontStyleSet* FuchsiaFontManager::onCreateStyleSet(int index) const {
NOTREACHED();
return nullptr;
}
SkFontStyleSet* FuchsiaFontManager::onMatchFamily(
const char family_name[]) const {
NOTREACHED();
return nullptr;
}
SkTypeface* FuchsiaFontManager::onMatchFamilyStyle(
const char family_name[],
const SkFontStyle& style) const {
std::string family_name_lowercase = base::ToLowerASCII(family_name);
fuchsia::fonts::FontRequest request;
auto it = font_map_.find(family_name_lowercase);
request.family = (it != font_map_.end()) ? it->second.c_str() : family_name;
request.weight = style.weight();
request.width = style.width();
request.slant = ToFontSlant(style.slant());
fuchsia::fonts::FontResponsePtr response;
auto status = font_provider_->GetFont(std::move(request), &response);
if (status.statvs != ZX_OK) {
ZX_DLOG(ERROR, status.statvs) << "Failed to query font provider.";
} else if (response) {
sk_sp<SkTypeface> result =
font_cache_->GetTypefaceFromFontData(std::move(response->data));
if (result)
return result.release();
LOG(ERROR) << "FontProvider returned invalid FontData for " << family_name;
}
// If Sans was requested and we failed to get a valid response from
// FontProvider then return |default_typeface_|. blink::FontCache queries Sans
// as a last-resort font. Returning |default_typeface_| here ensures that the
// renderer doesn't crash when FontProvider stops working.
if (family_name_lowercase == "sans") {
// Copy |default_typeface_| to increment ref-count before returning it.
sk_sp<SkTypeface> result = default_typeface_;
return result.release();
}
return nullptr;
}
SkTypeface* FuchsiaFontManager::onMatchFamilyStyleCharacter(
const char family_name[],
const SkFontStyle& style,
const char* bcp47[],
int bcp47Count,
SkUnichar character) const {
if (u_hasBinaryProperty(character, UCHAR_EMOJI))
return onMatchFamilyStyle("Noto Color Emoji", style);
return nullptr;
}
SkTypeface* FuchsiaFontManager::onMatchFaceStyle(
const SkTypeface* typeface,
const SkFontStyle& style) const {
NOTREACHED();
return nullptr;
}
sk_sp<SkTypeface> FuchsiaFontManager::onMakeFromData(sk_sp<SkData> data,
int ttc_index) const {
return makeFromStream(std::make_unique<SkMemoryStream>(std::move(data)),
ttc_index);
}
sk_sp<SkTypeface> FuchsiaFontManager::onMakeFromStreamIndex(
std::unique_ptr<SkStreamAsset> stream,
int ttc_index) const {
return makeFromStream(std::move(stream),
SkFontArguments().setCollectionIndex(ttc_index));
}
sk_sp<SkTypeface> FuchsiaFontManager::onMakeFromStreamArgs(
std::unique_ptr<SkStreamAsset> stream,
const SkFontArguments& args) const {
return CreateTypefaceFromSkStream(std::move(stream), args,
/*on_deleted=*/base::OnceClosure());
}
sk_sp<SkTypeface> FuchsiaFontManager::onMakeFromFile(const char path[],
int ttc_index) const {
NOTREACHED();
return nullptr;
}
sk_sp<SkTypeface> FuchsiaFontManager::onLegacyMakeTypeface(
const char family_name[],
SkFontStyle style) const {
sk_sp<SkTypeface> typeface;
if (family_name)
typeface.reset(this->onMatchFamilyStyle(family_name, style));
return typeface;
}
} // namespace skia
// 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.
#ifndef SKIA_EXT_FONTMGR_FUCHSIA_H_
#define SKIA_EXT_FONTMGR_FUCHSIA_H_
#include <memory>
#include <fuchsia/fonts/cpp/fidl.h>
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "third_party/skia/include/core/SkFontMgr.h"
#include "third_party/skia/include/core/SkStream.h"
#include "third_party/skia/include/core/SkTypeface.h"
namespace skia {
class FuchsiaFontManager : public SkFontMgr {
public:
explicit FuchsiaFontManager(
fuchsia::fonts::FontProviderSync2Ptr font_provider);
~FuchsiaFontManager() override;
protected:
// SkFontMgr overrides.
int onCountFamilies() const override;
void onGetFamilyName(int index, SkString* family_name) const override;
SkFontStyleSet* onMatchFamily(const char family_name[]) const override;
SkFontStyleSet* onCreateStyleSet(int index) const override;
SkTypeface* onMatchFamilyStyle(const char family_name[],
const SkFontStyle&) const override;
SkTypeface* onMatchFamilyStyleCharacter(const char family_name[],
const SkFontStyle&,
const char* bcp47[],
int bcp47_count,
SkUnichar character) const override;
SkTypeface* onMatchFaceStyle(const SkTypeface*,
const SkFontStyle&) const override;
sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttc_index) const override;
sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
int ttc_index) const override;
sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
const SkFontArguments&) const override;
sk_sp<SkTypeface> onMakeFromFile(const char path[],
int ttc_index) const override;
sk_sp<SkTypeface> onLegacyMakeTypeface(const char family_name[],
SkFontStyle) const override;
private:
class FontCache;
fuchsia::fonts::FontProviderSync2Ptr font_provider_;
// Map applied to font family name before sending requests to the FontService.
base::flat_map<std::string, std::string> font_map_;
// FontCache keeps all SkTypeface instances returned by the manager. It allows
// to ensure that SkTypeface object is created only once for each typeface.
std::unique_ptr<FontCache> font_cache_;
sk_sp<SkTypeface> default_typeface_;
DISALLOW_COPY_AND_ASSIGN(FuchsiaFontManager);
};
} // namespace skia
#endif // SKIA_EXT_FONTMGR_FUCHSIA_H_
// 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 "skia/ext/fontmgr_fuchsia.h"
#include <fuchsia/fonts/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>
#include "base/base_paths.h"
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/location.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/task_runner.h"
#include "base/threading/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace skia {
namespace {
constexpr zx_rights_t kFontDataRights =
ZX_RIGHTS_BASIC | ZX_RIGHT_READ | ZX_RIGHT_MAP;
fuchsia::fonts::FontData LoadFont(const base::FilePath& file_path) {
std::string file_content;
CHECK(ReadFileToString(file_path, &file_content));
fuchsia::fonts::FontData data;
zx_status_t status =
zx::vmo::create(file_content.size(), 0, &data.buffer.vmo);
ZX_CHECK(status == ZX_OK, status);
status = data.buffer.vmo.write(file_content.data(), 0, file_content.size());
ZX_CHECK(status == ZX_OK, status);
data.buffer.size = file_content.size();
return data;
}
class MockFontProvider : public fuchsia::fonts::FontProvider {
public:
MockFontProvider() {
base::FilePath assets_dir;
EXPECT_TRUE(base::PathService::Get(base::DIR_ASSETS, &assets_dir));
// Roboto is not in test_fonts. Just load some arbitrary fonts for the
// tests.
roboto_ = LoadFont(assets_dir.Append("test_fonts/Arimo-Regular.ttf"));
roboto_slab_ = LoadFont(assets_dir.Append("test_fonts/Tinos-Regular.ttf"));
}
// fuchsia::fonts::FontProvider implementation.
void GetFont(fuchsia::fonts::FontRequest request,
GetFontCallback callback) override {
fuchsia::fonts::FontData* font_data = nullptr;
if (*request.family == "Roboto") {
font_data = &roboto_;
} else if (*request.family == "RobotoSlab") {
font_data = &roboto_slab_;
}
if (!font_data) {
callback(nullptr);
return;
}
auto response = fuchsia::fonts::FontResponse::New();
EXPECT_EQ(font_data->buffer.vmo.duplicate(kFontDataRights,
&(response->data.buffer.vmo)),
ZX_OK);
response->data.buffer.size = font_data->buffer.size;
callback(std::move(response));
}
private:
fuchsia::fonts::FontData roboto_;
fuchsia::fonts::FontData roboto_slab_;
};
class MockFontProviderService {
public:
MockFontProviderService() : provider_thread_("MockFontProvider") {
provider_thread_.StartWithOptions(
base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
}
~MockFontProviderService() {
provider_thread_.task_runner()->DeleteSoon(FROM_HERE,
std::move(provider_binding_));
}
void Bind(fidl::InterfaceRequest<fuchsia::fonts::FontProvider> request) {
provider_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&MockFontProviderService::DoBind,
base::Unretained(this), std::move(request)));
}
private:
void DoBind(fidl::InterfaceRequest<fuchsia::fonts::FontProvider> request) {
provider_binding_ =
std::make_unique<fidl::Binding<fuchsia::fonts::FontProvider>>(
&provider_, std::move(request));
}
base::Thread provider_thread_;
MockFontProvider provider_;
std::unique_ptr<fidl::Binding<fuchsia::fonts::FontProvider>>
provider_binding_;
};
} // namespace
class FuchsiaFontManagerTest : public testing::Test {
public:
FuchsiaFontManagerTest() {
fuchsia::fonts::FontProviderSync2Ptr font_provider;
font_provider_service_.Bind(font_provider.NewRequest());
font_manager_ = sk_make_sp<FuchsiaFontManager>(std::move(font_provider));
}
protected:
MockFontProviderService font_provider_service_;
sk_sp<SkFontMgr> font_manager_;
};
// Verify that SkTypeface objects are cached.
TEST_F(FuchsiaFontManagerTest, Caching) {
sk_sp<SkTypeface> sans(
font_manager_->matchFamilyStyle("sans", SkFontStyle()));
sk_sp<SkTypeface> sans2(
font_manager_->matchFamilyStyle("sans", SkFontStyle()));
// Expect that the same SkTypeface is returned for both requests.
EXPECT_EQ(sans.get(), sans2.get());
// Request serif and verify that a different SkTypeface is returned.
sk_sp<SkTypeface> serif(
font_manager_->matchFamilyStyle("serif", SkFontStyle()));
EXPECT_NE(sans.get(), serif.get());
}
// Verify that SkTypeface can outlive the manager.
TEST_F(FuchsiaFontManagerTest, TypefaceOutlivesManager) {
sk_sp<SkTypeface> sans(
font_manager_->matchFamilyStyle("sans", SkFontStyle()));
font_manager_.reset();
}
// Verify that we can query a font after releasing a previous instance.
TEST_F(FuchsiaFontManagerTest, ReleaseThenCreateAgain) {
sk_sp<SkTypeface> serif(
font_manager_->matchFamilyStyle("serif", SkFontStyle()));
EXPECT_TRUE(serif);
serif.reset();
sk_sp<SkTypeface> serif2(
font_manager_->matchFamilyStyle("serif", SkFontStyle()));
EXPECT_TRUE(serif2);
}
} // namespace skia
\ No newline at end of file
......@@ -145,6 +145,17 @@ fuchsia_sdk_pkg("fit") {
]
}
fuchsia_sdk_fidl_pkg("fonts") {
namespace = "fuchsia"
namespace_path = "fuchsia"
sources = [
"font_provider.fidl",
]
deps = [
":mem",
]
}
fuchsia_sdk_fidl_pkg("gfx") {
namespace = "fuchsia.ui"
namespace_path = "fuchsia/ui"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment