Commit 7e00fed9 authored by Dominik Röttsches's avatar Dominik Röttsches Committed by Commit Bot

Add unique font name lookup table implementation and tests

Preparation for hooking up @font-face { src: local() } matching in
Blink. For now adding an implementation that extracts full font name and
postscript name from fonts given a set of directories to scan.

Includes unit tests for basic functionality, compatibility with TrueType
collections and for resilience against corrupted font files.

For the general design see https://crbug.com/828317.

Bug: 867877
Change-Id: I5f557d99a5be49671bd9aa13c1712b6f8a2aa96b
Reviewed-on: https://chromium-review.googlesource.com/1151348Reviewed-by: default avatarJochen Eisinger <jochen@chromium.org>
Reviewed-by: default avatarDavid Turner <digit@chromium.org>
Reviewed-by: default avatarEmil A Eklund <eae@chromium.org>
Commit-Queue: Dominik Röttsches <drott@chromium.org>
Cr-Commit-Position: refs/heads/master@{#579785}
parent 56e969f5
......@@ -2172,6 +2172,12 @@ jumbo_source_set("browser") {
"android/tracing_controller_android.h",
"android/web_contents_observer_proxy.cc",
"android/web_contents_observer_proxy.h",
"font_unique_name_lookup/font_table_matcher.cc",
"font_unique_name_lookup/font_table_matcher.h",
"font_unique_name_lookup/font_unique_name_lookup.cc",
"font_unique_name_lookup/font_unique_name_lookup.h",
"font_unique_name_lookup/icu_fold_case_util.cc",
"font_unique_name_lookup/icu_fold_case_util.h",
"frame_host/render_frame_host_android.cc",
"frame_host/render_frame_host_android.h",
"media/capture/screen_capture_device_android.cc",
......@@ -2218,11 +2224,14 @@ jumbo_source_set("browser") {
]
deps += [
":reflection_jni_headers",
"//build/config/freetype",
"//content/browser/font_unique_name_lookup:font_unique_name_table_proto",
"//content/public/android:jni",
"//device/gamepad/public/mojom",
"//media",
"//media/capture/content/android",
"//media/capture/video/android",
"//third_party/icu",
"//ui/accessibility:ax_assistant",
"//ui/accessibility/mojom",
"//ui/android",
......
# 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.
import("//build/config/freetype/freetype.gni")
import("//third_party/protobuf/proto_library.gni")
assert(is_android)
proto_library("font_unique_name_table_proto") {
sources = [
"font_unique_name_table.proto",
]
}
drott@chromium.org
# COMPONENT: Blink>Fonts
// 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 "content/browser/font_unique_name_lookup/font_table_matcher.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/font_unique_name_lookup/icu_fold_case_util.h"
#include <algorithm>
namespace content {
FontTableMatcher::FontTableMatcher(
const base::ReadOnlySharedMemoryMapping& mapping) {
font_table_.ParseFromArray(mapping.memory(), mapping.size());
}
// static
base::ReadOnlySharedMemoryMapping
FontTableMatcher::MemoryMappingFromFontUniqueNameTable(
const FontUniqueNameTable& font_unique_name_table) {
size_t serialization_size = font_unique_name_table.ByteSizeLong();
CHECK(serialization_size);
base::MappedReadOnlyRegion mapped_region =
base::ReadOnlySharedMemoryRegion::Create(serialization_size);
font_unique_name_table.SerializeToArray(mapped_region.mapping.memory(),
mapped_region.mapping.mapped_size());
return mapped_region.region.Map();
}
base::Optional<FontTableMatcher::MatchResult> FontTableMatcher::MatchName(
const std::string& name_request) const {
std::string folded_name_request = IcuFoldCase(name_request);
const auto& font_entries = font_table_.font_entries();
auto find_result = std::find_if(
font_entries.begin(), font_entries.end(),
[&folded_name_request](
const FontUniqueNameTable_FontUniqueNameEntry& entry) {
return !entry.postscript_name().compare(folded_name_request) ||
!entry.full_name().compare(folded_name_request);
});
if (find_result != font_entries.end()) {
return base::Optional<MatchResult>(
{find_result->file_path(), find_result->ttc_index()});
}
return {};
}
size_t FontTableMatcher::AvailableFonts() const {
return font_table_.font_entries_size();
}
bool FontTableMatcher::FontListIsDisjointFrom(
const FontTableMatcher& other) const {
std::vector<std::string> paths_self, paths_other, intersection_result;
for (const auto& indexed_font : font_table_.font_entries()) {
paths_self.push_back(indexed_font.file_path());
}
for (const auto& indexed_font_other : other.font_table_.font_entries()) {
paths_other.push_back(indexed_font_other.file_path());
}
std::sort(paths_self.begin(), paths_self.end());
std::sort(paths_other.begin(), paths_other.end());
std::set_intersection(paths_self.begin(), paths_self.end(),
paths_other.begin(), paths_other.end(),
std::back_inserter(intersection_result));
return intersection_result.empty();
}
} // namespace content
// 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 CONTENT_BROWSER_FONT_UNIQUE_NAME_LOOKUP_FONT_TABLE_MATCHER_H_
#define CONTENT_BROWSER_FONT_UNIQUE_NAME_LOOKUP_FONT_TABLE_MATCHER_H_
#include "base/memory/read_only_shared_memory_region.h"
#include "content/browser/font_unique_name_lookup/font_unique_name_table.pb.h"
#include "content/common/content_export.h"
#include <stddef.h>
#include <stdint.h>
namespace content {
// Parses a protobuf received in memory_mapping to build a font lookup
// structure. Allows case-insensitively matching full font names or postscript
// font names aginst the parsed table by calling MatchName. Used in Blink for
// looking up
// @font-face { src: local(<font_name>) } CSS font face src references.
class CONTENT_EXPORT FontTableMatcher {
public:
// Constructs a FontTableMatcher from a ReadOnlySharedMemoryMapping returned
// by FontUniqueNameLookup. Internally parses the Protobuf structure in
// memory_mapping to build a list of unique font names, which can then be
// matched using the MatchName method. The ReadOnlySharedMemoryMapping passed
// in memory_mapping only needs to be alive for the initial construction of
// FontTableMatcher. After that, FontTableMatcher no longer accesses it.
explicit FontTableMatcher(
const base::ReadOnlySharedMemoryMapping& memory_mapping);
// Takes a FontUniqueNameTable protobuf and serializes it into a newly created
// ReadonlySharedMemoryMapping. Used only for testing.
static base::ReadOnlySharedMemoryMapping MemoryMappingFromFontUniqueNameTable(
const FontUniqueNameTable& font_unique_name_table);
struct MatchResult {
std::string font_path;
uint32_t ttc_index;
};
// Given a font full name or font potscript name, match case insensitively
// against the internal list of unique font names.
// Return a font filesystem path and a TrueType collection index to identify a
// font binary to uniquely identify instantiate a font.
base::Optional<MatchResult> MatchName(const std::string& name_request) const;
// Returns the number of fonts available after parsing the
// ReadOnlySharedMemoryMapping.
size_t AvailableFonts() const;
// Compares this FontTableMatcher to other for whether
// their internal list of fonts is disjoint. Used only for testing.
bool FontListIsDisjointFrom(const FontTableMatcher& other) const;
private:
FontUniqueNameTable font_table_;
};
} // namespace content
#endif // CONTENT_BROWSER_FONT_UNIQUE_NAME_LOOKUP_FONT_TABLE_MATCHER_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 "content/browser/font_unique_name_lookup/font_table_matcher.h"
#include "content/browser/font_unique_name_lookup/icu_fold_case_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kTestFilePath1[] = "tmp/test/font1.ttf";
const char kDummyAndroidBuildFingerPrint[] = "A";
void PopulateFontUniqueNameEntry(
content::FontUniqueNameTable_FontUniqueNameEntry* entry,
const std::string& path,
int32_t ttc_index,
const std::string& full_name,
const std::string& postscript_name) {
entry->set_file_path(path);
entry->set_ttc_index(ttc_index);
entry->set_full_name(content::IcuFoldCase(full_name));
entry->set_postscript_name(content::IcuFoldCase(postscript_name));
}
} // namespace
namespace content {
class FontTableMatcherTest : public ::testing::Test {
protected:
void SetUp() override {
FontUniqueNameTable font_unique_name_table;
font_unique_name_table.set_stored_for_android_build_fp(
kDummyAndroidBuildFingerPrint);
PopulateFontUniqueNameEntry(font_unique_name_table.add_font_entries(),
kTestFilePath1, 0, "FONT NAME UPPERCASE",
"FONT-NAME-UPPERCASE");
base::ReadOnlySharedMemoryMapping mapping =
FontTableMatcher::MemoryMappingFromFontUniqueNameTable(
std::move(font_unique_name_table));
matcher_ = std::make_unique<FontTableMatcher>(mapping);
}
std::unique_ptr<FontTableMatcher> matcher_;
};
TEST_F(FontTableMatcherTest, CaseInsensitiveMatchingBothNames) {
ASSERT_EQ(matcher_->AvailableFonts(), 1u);
base::Optional<FontTableMatcher::MatchResult> result =
matcher_->MatchName("font name uppercase");
ASSERT_TRUE(result.has_value());
ASSERT_EQ(result->font_path, kTestFilePath1);
ASSERT_EQ(result->ttc_index, 0u);
result = matcher_->MatchName("font-name-uppercase");
ASSERT_TRUE(result.has_value());
ASSERT_EQ(result->font_path, kTestFilePath1);
ASSERT_EQ(result->ttc_index, 0u);
}
TEST_F(FontTableMatcherTest, NoSubStringMatching) {
ASSERT_EQ(matcher_->AvailableFonts(), 1u);
base::Optional<FontTableMatcher::MatchResult> result =
matcher_->MatchName("font name");
ASSERT_FALSE(result.has_value());
result = matcher_->MatchName("font-name");
ASSERT_FALSE(result.has_value());
}
} // namespace content
// 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 "content/browser/font_unique_name_lookup/font_unique_name_lookup.h"
#include "base/android/build_info.h"
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/font_unique_name_lookup/font_unique_name_table.pb.h"
#include "content/browser/font_unique_name_lookup/icu_fold_case_util.h"
#include <set>
#include <vector>
#include "third_party/icu/source/common/unicode/unistr.h"
#include FT_TRUETYPE_IDS_H
namespace {
using namespace ::icu_62;
const char kProtobufFilename[] = "font_unique_name_table.pb";
static const char* const kAndroidFontPaths[] = {"/system/fonts",
"/vendor/fonts"};
bool SfntNameIsEnglish(const FT_SfntName& sfnt_name) {
if (sfnt_name.platform_id == TT_PLATFORM_MICROSOFT)
return sfnt_name.language_id == TT_MS_LANGID_ENGLISH_UNITED_STATES;
if (sfnt_name.platform_id == TT_PLATFORM_MACINTOSH)
return sfnt_name.language_id == TT_MAC_LANGID_ENGLISH;
return false;
}
// Convenience scoped wrapper for FT_Face instances. Takes care of handling
// FreeType memory by calling FT_Done_Face on destruction.
class ScopedFtFace {
public:
// Create a new FT_Face instance that will be wrapped by this object.
// Call IsValid() after construction to check for errors.
// |library| is the parent FT_Library instance, |font_path| the input font
// file path, and |ttc_index| the font file index (for TrueType collections).
ScopedFtFace(FT_Library library,
const std::string& font_path,
int32_t ttc_index)
: ft_face_(nullptr),
ft_error_(
FT_New_Face(library, font_path.c_str(), ttc_index, &ft_face_)) {}
// Destructor will destroy the FT_Face instance automatically.
~ScopedFtFace() {
if (IsValid()) {
FT_Done_Face(ft_face_);
}
}
// Returns true iff instance is valid, i.e. construction did not fail.
bool IsValid() const { return ft_error_ == FT_Err_Ok; }
// Return FreeType error code from construction.
FT_Error error() const { return ft_error_; }
// Returns FT_Face value.
FT_Face get() const { return ft_face_; }
private:
FT_Face ft_face_ = nullptr;
FT_Error ft_error_ = FT_Err_Ok;
};
} // namespace
namespace content {
class PlatformFontUniqueNameLookup : public FontUniqueNameLookup {
public:
PlatformFontUniqueNameLookup() : FontUniqueNameLookup(GetCacheDirectory()) {
// Error from LoadFromFile() is ignored: Loading the cache file could be
// recovered from by rebuilding the font table. UpdateTableIfNeeded() checks
// whether the internal base::MappedReadOnlyRegion has a size, which it
// doesn't if the LoadFromFile() failed. If it doesn't have a size, the
// table is rebuild by calling UpdateTable().
LoadFromFile();
if (UpdateTableIfNeeded()) {
// TODO(drott): Add UMA histograms for recording cache read and write
// failures.
PersistToFile();
}
}
private:
static base::FilePath GetCacheDirectory() {
base::FilePath cache_directory;
base::PathService::Get(base::DIR_CACHE, &cache_directory);
return cache_directory;
}
};
FontUniqueNameLookup& FontUniqueNameLookup::GetInstance() {
static base::NoDestructor<PlatformFontUniqueNameLookup> sInstance;
return *sInstance.get();
}
FontUniqueNameLookup::FontUniqueNameLookup(FontUniqueNameLookup&&) = default;
FontUniqueNameLookup::FontUniqueNameLookup(
const base::FilePath& cache_directory)
: cache_directory_(cache_directory) {
if (!DirectoryExists(cache_directory_) ||
!base::PathIsWritable(cache_directory_)) {
DCHECK(false) << "Error accessing cache directory for writing: "
<< cache_directory_.value();
cache_directory_ = base::FilePath();
}
FT_Init_FreeType(&ft_library_);
}
FontUniqueNameLookup::~FontUniqueNameLookup() {
FT_Done_FreeType(ft_library_);
}
base::ReadOnlySharedMemoryRegion
FontUniqueNameLookup::GetUniqueNameTableAsSharedMemoryRegion() const {
return proto_storage_.region.Duplicate();
}
bool FontUniqueNameLookup::IsValid() {
return proto_storage_.IsValid() && proto_storage_.mapping.size();
}
bool FontUniqueNameLookup::UpdateTableIfNeeded() {
FontUniqueNameTable font_table;
bool update_needed =
!proto_storage_.IsValid() || !proto_storage_.mapping.size() ||
!font_table.ParseFromArray(proto_storage_.mapping.memory(),
proto_storage_.mapping.size()) ||
font_table.stored_for_android_build_fp() != GetAndroidBuildFingerprint();
if (update_needed)
UpdateTable();
return update_needed;
}
bool FontUniqueNameLookup::UpdateTable() {
std::vector<std::string> font_files_to_index = GetFontFilePaths();
FontUniqueNameTable font_table;
font_table.set_stored_for_android_build_fp(GetAndroidBuildFingerprint());
for (const auto& font_file : font_files_to_index) {
int32_t number_of_faces = NumberOfFacesInFontFile(font_file);
for (int32_t i = 0; i < number_of_faces; ++i) {
if (!IndexFile(font_table.add_font_entries(), font_file, i)) {
// TODO(drott): Track file scanning failures in UMA.
font_table.mutable_font_entries()->RemoveLast();
}
}
}
proto_storage_ =
base::ReadOnlySharedMemoryRegion::Create(font_table.ByteSizeLong());
if (!IsValid())
return false;
if (!font_table.SerializeToArray(proto_storage_.mapping.memory(),
proto_storage_.mapping.size())) {
proto_storage_ = base::MappedReadOnlyRegion();
return false;
}
return true;
}
bool FontUniqueNameLookup::LoadFromFile() {
// Reset to empty to ensure IsValid() is false if reading fails.
proto_storage_ = base::MappedReadOnlyRegion();
base::File table_cache_file(
TableCacheFilePath(),
base::File::FLAG_OPEN | base::File::Flags::FLAG_READ);
if (!table_cache_file.IsValid())
return false;
proto_storage_ =
base::ReadOnlySharedMemoryRegion::Create(table_cache_file.GetLength());
if (!IsValid())
return false;
int read_result = table_cache_file.Read(
0, static_cast<char*>(proto_storage_.mapping.memory()),
table_cache_file.GetLength());
// If no bytes were read or Read() returned -1 we are not able to reconstruct
// a font table from the cached file.
if (read_result <= 0) {
proto_storage_ = base::MappedReadOnlyRegion();
return false;
}
FontUniqueNameTable font_table;
if (!font_table.ParseFromArray(proto_storage_.mapping.memory(),
proto_storage_.mapping.size())) {
proto_storage_ = base::MappedReadOnlyRegion();
return false;
}
return true;
}
bool FontUniqueNameLookup::PersistToFile() {
DCHECK(IsValid());
if (!IsValid())
return false;
base::File table_cache_file(
TableCacheFilePath(),
base::File::FLAG_CREATE_ALWAYS | base::File::Flags::FLAG_WRITE);
if (!table_cache_file.IsValid())
return false;
if (table_cache_file.Write(
0, static_cast<char*>(proto_storage_.mapping.memory()),
proto_storage_.mapping.size()) == -1) {
table_cache_file.SetLength(0);
proto_storage_ = base::MappedReadOnlyRegion();
return false;
}
return true;
}
base::FilePath FontUniqueNameLookup::TableCacheFilePath() {
return base::FilePath(
cache_directory_.Append(base::FilePath(kProtobufFilename)));
}
bool FontUniqueNameLookup::IndexFile(
FontUniqueNameTable_FontUniqueNameEntry* font_entry,
const std::string& font_file_path,
uint32_t ttc_index) {
ScopedFtFace face(ft_library_, font_file_path.c_str(), ttc_index);
if (!face.IsValid()) {
LOG(ERROR) << "Unable to open font file for indexing: "
<< font_file_path.c_str()
<< " - FreeType FT_Error code: " << face.error();
return false;
}
if (!FT_Get_Sfnt_Name_Count(face.get())) {
LOG(ERROR) << "Zero name table entries in font file: "
<< font_file_path.c_str();
return false;
}
// Get file attributes
base::File font_file_for_info(
base::FilePath(font_file_path.c_str()),
base::File::FLAG_OPEN | base::File::Flags::FLAG_READ);
if (!font_file_for_info.IsValid()) {
LOG(ERROR) << "Unable to open font file: " << font_file_path.c_str();
return false;
}
base::File::Info font_file_info;
if (!font_file_for_info.GetInfo(&font_file_info)) {
LOG(ERROR) << "Unable to get font file attributes for: "
<< font_file_path.c_str();
return false;
}
font_entry->set_file_path(font_file_path);
font_entry->set_ttc_index(ttc_index);
for (size_t i = 0; i < FT_Get_Sfnt_Name_Count(face.get()); ++i) {
FT_SfntName sfnt_name;
if (FT_Get_Sfnt_Name(face.get(), i, &sfnt_name) != 0) {
LOG(ERROR) << "Unable to retrieve Sfnt Name table for font file: "
<< font_file_path.c_str();
return false;
}
if (!SfntNameIsEnglish(sfnt_name))
continue;
std::string sfnt_name_string = "";
std::string codepage_name;
// Codepage names from http://demo.icu-project.org/icu-bin/convexp
if (sfnt_name.platform_id == TT_PLATFORM_MICROSOFT &&
sfnt_name.encoding_id == TT_MS_ID_UNICODE_CS) {
codepage_name = "UTF16-BE";
} else if (sfnt_name.platform_id == TT_PLATFORM_MACINTOSH &&
sfnt_name.encoding_id == TT_MAC_ID_ROMAN) {
codepage_name = "macintosh";
}
UnicodeString sfnt_name_unicode(reinterpret_cast<char*>(sfnt_name.string),
sfnt_name.string_len,
codepage_name.c_str());
if (sfnt_name_unicode.isBogus())
return false;
// Firefox performs case insensitive matching for src: local().
sfnt_name_unicode.foldCase();
sfnt_name_unicode.toUTF8String(sfnt_name_string);
switch (sfnt_name.name_id) {
case TT_NAME_ID_PS_NAME:
font_entry->set_postscript_name(IcuFoldCase(sfnt_name_string));
break;
case TT_NAME_ID_FULL_NAME:
font_entry->set_full_name(IcuFoldCase(sfnt_name_string));
break;
default:
break;
}
}
return true;
}
int32_t FontUniqueNameLookup::NumberOfFacesInFontFile(
const std::string& font_filename) const {
// According to FreeType documentation calling FT_Open_Face with a negative
// index value allows us to probe how many fonts can be found in a font file
// (which can be a single font ttf or a TrueType collection (.ttc).
ScopedFtFace probe_face(ft_library_, font_filename.c_str(), -1);
if (!probe_face.IsValid())
return 0;
return probe_face.get()->num_faces;
}
std::string FontUniqueNameLookup::GetAndroidBuildFingerprint() const {
return android_build_fingerprint_for_testing_.size()
? android_build_fingerprint_for_testing_
: base::android::BuildInfo::GetInstance()->android_build_fp();
}
std::vector<std::string> FontUniqueNameLookup::GetFontFilePaths() const {
if (font_file_paths_for_testing_.size())
return font_file_paths_for_testing_;
std::vector<std::string> font_files;
for (const char* font_dir_path : kAndroidFontPaths) {
base::FileEnumerator files_enumerator(
base::MakeAbsoluteFilePath(base::FilePath(font_dir_path)), true,
base::FileEnumerator::FILES);
for (base::FilePath name = files_enumerator.Next(); !name.empty();
name = files_enumerator.Next()) {
if (name.Extension() == ".ttf" || name.Extension() == ".ttc" ||
name.Extension() == ".otf") {
font_files.push_back(name.value());
}
}
}
return font_files;
}
} // namespace content
// 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 CONTENT_BROWSER_FONT_UNIQUE_NAME_LOOKUP_FONT_UNIQUE_NAME_LOOKUP_H_
#define CONTENT_BROWSER_FONT_UNIQUE_NAME_LOOKUP_FONT_UNIQUE_NAME_LOOKUP_H_
#include "base/files/file_path.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "content/common/content_export.h"
#include <ft2build.h>
#include FT_SYSTEM_H
#include FT_TRUETYPE_TABLES_H
#include FT_SFNT_NAMES_H
#include <string>
namespace content {
class FontUniqueNameTable_FontUniqueNameEntry;
// Scans a set of font files for the full font name and postscript name
// information in the name table and builds a Protobuf lookup structure from
// it. The protobuf can be persisted to disk to the Android cache directory, and
// it can be read from disk as well. Provides the lookup structure as a
// ReadOnlySharedMemoryRegion. Performing lookup on it is done through
// FontTableMatcher.
class CONTENT_EXPORT FontUniqueNameLookup {
public:
FontUniqueNameLookup() = delete;
// Retrieve an initialized instance of FontUniqueNameLookup that has read the
// table from cache if there was one, updated the lookup table if needed
// (i.e. if there was an Android firmware update) from the standard Android
// font directories, and written the updated lookup table back to file. It is
// ready to use with FontTableMatcher.
static FontUniqueNameLookup& GetInstance();
// Construct a FontUniqueNameLookup given a cache directory path
// |cache_directory| to persist the internal lookup table, a
// FontFilesCollector to enumerate font files and a BuildFingerprintProvider
// to access the Android build fingerprint.
FontUniqueNameLookup(const base::FilePath& cache_directory);
~FontUniqueNameLookup();
// Default move contructor.
FontUniqueNameLookup(FontUniqueNameLookup&&);
// Default move assigment operator.
FontUniqueNameLookup& operator=(FontUniqueNameLookup&&) = default;
// Return a ReadOnlySharedMemoryRegion to access the serialized form of the
// current lookup table. To be used with FontTableMatcher.
base::ReadOnlySharedMemoryRegion GetUniqueNameTableAsSharedMemoryRegion()
const;
// Returns true if an up-to-date, consistent font table is present.
bool IsValid();
// If an Android firmware update was detected by checking
// BuildFingerprintProvider, call UpdateTable(). Do not use this method.
// Instead, call GetInstance() to get an initialized instance. Publicly
// exposed for testing.
bool UpdateTableIfNeeded();
// Rescan the files returned by the FontFilesCollector and rebuild the lookup
// table by indexing them. Do not use this method. Instead, call GetInstance()
// to get an initialized instance. Returns true if instance is valid after
// updating, returns false if an error occured in acquiring memory or
// serializing the scanned files to the shared memory region. Publicly exposed
// for testing.
bool UpdateTable();
// Try to find a serialized lookup table in the directory specified at
// construction and load it into memory. Do not use this method. Instead, call
// GetInstance() to get an initialized instance. Publicly exposed for testing.
bool LoadFromFile();
// Serialize the current lookup table into a file in the the cache directory
// specified at construction time. If an up to date table is present and
// persisting fails, discard the internal table, as it might be that we were
// not able to update the file the previous time. Do not use this
// method. Instead, call GetInstance() to get an initialized
// instance. Publicly exposed for testing.
bool PersistToFile();
// Override the internal font files enumeration with an explicit set of fonts
// to be scanned in |font_file_paths|. Only used for testing.
void SetFontFilePathsForTesting(
const std::vector<std::string> font_file_paths) {
font_file_paths_for_testing_ = font_file_paths;
}
// Override the Android build fingerprint for testing.
void SetAndroidBuildFingerprintForTesting(
const std::string& build_fingerprint_override) {
android_build_fingerprint_for_testing_ = build_fingerprint_override;
}
// Returns the storage location of the table cache protobuf file.
base::FilePath TableCacheFilePathForTesting() {
return TableCacheFilePath();
};
private:
// Scan the font file at |font_file_path| and given |ttc_index| and extract
// full font name and postscript name from the font and store it into the
// font_index_entry protobuf object.
bool IndexFile(FontUniqueNameTable_FontUniqueNameEntry* font_index_entry,
const std::string& font_file_path,
uint32_t ttc_index);
// For a TrueType font collection, determine how many font faces are
// available in a file.
int32_t NumberOfFacesInFontFile(const std::string& font_filename) const;
// If an Android build fingerprint override is set through
// SetAndroidBuildFingerprint() return that, otherwise return the actual
// platform's Android build fingerprint.
std::string GetAndroidBuildFingerprint() const;
// If an override is set through SetFontFilePathsForTesting() return those
// fonts, otherwise enumerate font files in the the Android platform font
// directories.
std::vector<std::string> GetFontFilePaths() const;
base::FilePath TableCacheFilePath();
base::FilePath cache_directory_;
FT_Library ft_library_;
base::MappedReadOnlyRegion proto_storage_;
std::string android_build_fingerprint_for_testing_ = "";
std::vector<std::string> font_file_paths_for_testing_ =
std::vector<std::string>();
};
} // namespace content
#endif // CONTENT_BROWSER_FONT_UNIQUE_NAME_LOOKUP_FONT_UNIQUE_NAME_LOOKUP_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 "testing/gtest/include/gtest/gtest.h"
#include "base/android/build_info.h"
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "content/browser/font_unique_name_lookup/font_table_matcher.h"
#include "content/browser/font_unique_name_lookup/font_unique_name_lookup.h"
#include <functional>
#include <memory>
namespace {
static const char* const kAndroidFontPaths[] = {"/system/fonts",
"/vendor/fonts"};
// Full font name, postscript name, filename.
static const char* const kRobotoCondensedBoldItalicNames[] = {
"Roboto Condensed Bold Italic", "RobotoCondensed-BoldItalic",
"RobotoCondensed-BoldItalic.ttf"};
std::vector<std::string> AndroidFontFilesList() {
std::vector<std::string> font_files;
for (const char* font_dir_path : kAndroidFontPaths) {
base::FileEnumerator files_enumerator(
base::MakeAbsoluteFilePath(base::FilePath(font_dir_path)), true,
base::FileEnumerator::FILES);
for (base::FilePath name = files_enumerator.Next(); !name.empty();
name = files_enumerator.Next()) {
if (name.Extension() == ".ttf" || name.Extension() == ".ttc" ||
name.Extension() == ".otf") {
font_files.push_back(name.value());
}
}
}
return font_files;
}
std::vector<std::string> SplitFontFilesList(
const std::vector<std::string> font_files,
bool return_second_half) {
CHECK_GT(font_files.size(), 2u);
auto start_copy = font_files.begin();
auto end_copy = font_files.begin() + (font_files.size() / 2);
if (return_second_half) {
start_copy = end_copy;
end_copy = font_files.end();
}
return std::vector<std::string>(start_copy, end_copy);
}
enum class TruncateLength { TruncateToZero, TruncateHalf };
void TruncateFile(const base::FilePath& file_path,
TruncateLength truncate_length) {
base::File file_to_truncate(
file_path, base::File::FLAG_OPEN | base::File::Flags::FLAG_WRITE);
size_t truncate_to = truncate_length == TruncateLength::TruncateHalf
? file_to_truncate.GetLength() / 2
: 0;
file_to_truncate.SetLength(truncate_to);
}
} // namespace
namespace content {
class FontUniqueNameLookupTest : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
font_unique_name_lookup_ =
std::make_unique<FontUniqueNameLookup>(temp_dir_.GetPath());
}
base::ScopedTempDir temp_dir_;
std::unique_ptr<FontUniqueNameLookup> font_unique_name_lookup_;
};
TEST_F(FontUniqueNameLookupTest, TestBuildLookup) {
ASSERT_TRUE(font_unique_name_lookup_->UpdateTable());
base::ReadOnlySharedMemoryMapping mapping =
font_unique_name_lookup_->GetUniqueNameTableAsSharedMemoryRegion().Map();
FontTableMatcher matcher(mapping);
ASSERT_GT(matcher.AvailableFonts(), 0u);
ASSERT_TRUE(font_unique_name_lookup_->PersistToFile());
ASSERT_TRUE(font_unique_name_lookup_->LoadFromFile());
FontTableMatcher matcher_after_load(
font_unique_name_lookup_->GetUniqueNameTableAsSharedMemoryRegion().Map());
ASSERT_GT(matcher_after_load.AvailableFonts(), 0u);
}
TEST_F(FontUniqueNameLookupTest, TestHandleFailedRead) {
base::DeleteFile(font_unique_name_lookup_->TableCacheFilePathForTesting(),
false);
ASSERT_FALSE(font_unique_name_lookup_->LoadFromFile());
ASSERT_FALSE(font_unique_name_lookup_->IsValid());
ASSERT_TRUE(font_unique_name_lookup_->UpdateTable());
ASSERT_TRUE(font_unique_name_lookup_->IsValid());
base::ReadOnlySharedMemoryMapping mapping =
font_unique_name_lookup_->GetUniqueNameTableAsSharedMemoryRegion().Map();
FontTableMatcher matcher(mapping);
ASSERT_GT(matcher.AvailableFonts(), 0u);
ASSERT_TRUE(font_unique_name_lookup_->PersistToFile());
ASSERT_TRUE(font_unique_name_lookup_->LoadFromFile());
ASSERT_TRUE(font_unique_name_lookup_->IsValid());
TruncateFile(font_unique_name_lookup_->TableCacheFilePathForTesting(),
TruncateLength::TruncateHalf);
ASSERT_FALSE(font_unique_name_lookup_->LoadFromFile());
ASSERT_FALSE(font_unique_name_lookup_->IsValid());
TruncateFile(font_unique_name_lookup_->TableCacheFilePathForTesting(),
TruncateLength::TruncateToZero);
ASSERT_FALSE(font_unique_name_lookup_->LoadFromFile());
ASSERT_FALSE(font_unique_name_lookup_->IsValid());
}
TEST_F(FontUniqueNameLookupTest, TestMatchPostScriptName) {
ASSERT_TRUE(font_unique_name_lookup_->UpdateTable());
FontTableMatcher matcher(
font_unique_name_lookup_->GetUniqueNameTableAsSharedMemoryRegion().Map());
ASSERT_GT(matcher.AvailableFonts(), 0u);
auto match_result = matcher.MatchName(kRobotoCondensedBoldItalicNames[1]);
ASSERT_TRUE(match_result);
ASSERT_TRUE(EndsWith(match_result->font_path,
kRobotoCondensedBoldItalicNames[2],
base::CompareCase::SENSITIVE));
base::File found_file(base::FilePath(match_result->font_path),
base::File::FLAG_OPEN | base::File::FLAG_READ);
ASSERT_TRUE(found_file.IsValid());
ASSERT_EQ(match_result->ttc_index, 0u);
}
TEST_F(FontUniqueNameLookupTest, TestMatchPostScriptNameTtc) {
if (base::android::BuildInfo::GetInstance()->sdk_int() <
base::android::SdkVersion::SDK_VERSION_NOUGAT) {
// Pre-Nougat Android does not contain any .ttc files as system fonts.
return;
}
ASSERT_TRUE(font_unique_name_lookup_->UpdateTable());
FontTableMatcher matcher(
font_unique_name_lookup_->GetUniqueNameTableAsSharedMemoryRegion().Map());
std::vector<std::string> ttc_postscript_names = {
"NotoSansCJKjp-Regular", "NotoSansCJKkr-Regular",
"NotoSansCJKsc-Regular", "NotoSansCJKtc-Regular",
"NotoSansMonoCJKjp-Regular", "NotoSansMonoCJKkr-Regular",
"NotoSansMonoCJKsc-Regular", "NotoSansMonoCJKtc-Regular",
};
for (size_t i = 0; i < ttc_postscript_names.size(); ++i) {
auto match_result = matcher.MatchName(ttc_postscript_names[i]);
ASSERT_TRUE(match_result);
ASSERT_TRUE(EndsWith(match_result->font_path, "NotoSansCJK-Regular.ttc",
base::CompareCase::SENSITIVE));
base::File found_file(base::FilePath(match_result->font_path),
base::File::FLAG_OPEN | base::File::FLAG_READ);
ASSERT_TRUE(found_file.IsValid());
ASSERT_EQ(match_result->ttc_index, i);
}
}
TEST_F(FontUniqueNameLookupTest, TestMatchFullFontName) {
ASSERT_TRUE(font_unique_name_lookup_->UpdateTable());
FontTableMatcher matcher(
font_unique_name_lookup_->GetUniqueNameTableAsSharedMemoryRegion().Map());
auto match_result = matcher.MatchName(kRobotoCondensedBoldItalicNames[0]);
ASSERT_TRUE(match_result);
ASSERT_TRUE(EndsWith(match_result->font_path,
kRobotoCondensedBoldItalicNames[2],
base::CompareCase::SENSITIVE));
base::File found_file(base::FilePath(match_result->font_path),
base::File::FLAG_OPEN | base::File::Flags::FLAG_READ);
ASSERT_TRUE(found_file.IsValid());
ASSERT_EQ(match_result->ttc_index, 0u);
}
namespace {
size_t GetNumTables(base::File& font_file) {
font_file.Seek(base::File::FROM_BEGIN, 5);
uint8_t num_tables_bytes[2] = {};
font_file.ReadAtCurrentPos(reinterpret_cast<char*>(num_tables_bytes),
base::size(num_tables_bytes));
uint16_t num_tables =
static_cast<uint16_t>(num_tables_bytes[0] + (num_tables_bytes[1] << 8));
return num_tables;
};
const size_t kOffsetTableRecords = 13;
const size_t kSizeOneTableRecord = 16;
} // namespace
// Creates a temp directory and copies Android font files to this
// directory. Provides two methods to inject faults into the font files 1)
// ZeroOutTableRecords writes a sequence of 0 to where the font table offset
// should be stored in the font file. 2) ZeroAfterTableIndex writes 0 from after
// the table records until the end of the file.
class FontFileCorruptor {
public:
FontFileCorruptor() {
CHECK(temp_dir_.CreateUniqueTempDir());
CopyPlatformFilesToTempDir();
}
// Overwrite the list of table records with 0.
void ZeroOutTableRecords() {
ForEachCopiedFontFile([](base::File& font_file) {
// Read number of font tables, then zero out the table record structure.
// https://docs.microsoft.com/en-us/typography/opentype/spec/font-file
size_t num_tables = GetNumTables(font_file);
CHECK_GT(num_tables, 0u);
char garbage[kSizeOneTableRecord] = {0};
for (size_t i = 0; i < num_tables; ++i) {
CHECK_EQ(static_cast<int>(kSizeOneTableRecord),
font_file.Write(kOffsetTableRecords + i * kSizeOneTableRecord,
garbage, base::size(garbage)));
}
});
}
// Overwrite the data in the font file with zeroes from after the table
// records until the end of the file.
void ZeroAfterTableIndex() {
ForEachCopiedFontFile([](base::File& font_file) {
size_t num_tables = GetNumTables(font_file);
CHECK_GT(num_tables, 0u);
const size_t offset_after_table_records =
kOffsetTableRecords + num_tables * kSizeOneTableRecord;
std::vector<char> zeroes;
zeroes.resize(font_file.GetLength() - offset_after_table_records);
std::fill(zeroes.begin(), zeroes.end(), 0);
CHECK_EQ(static_cast<int>(zeroes.size()),
font_file.Write(offset_after_table_records, zeroes.data(),
zeroes.size()));
});
}
// Get the list of filenames copied to the temporary directory.
std::vector<std::string> GetFontFilesList() { return copied_files_; }
private:
void ForEachCopiedFontFile(std::function<void(base::File&)> manipulate_file) {
for (const auto& filename : copied_files_) {
base::File font_file(base::FilePath(filename),
base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WRITE);
manipulate_file(font_file);
}
}
void CopyPlatformFilesToTempDir() {
std::vector<std::string> platform_files = AndroidFontFilesList();
for (auto& font_file : platform_files) {
base::FilePath source_path(font_file);
base::FilePath destination_path(temp_dir_.GetPath());
destination_path = destination_path.Append(source_path.BaseName());
if (base::CopyFile(source_path, destination_path))
copied_files_.push_back(destination_path.value());
}
}
base::ScopedTempDir temp_dir_;
std::vector<std::string> copied_files_;
};
class FaultInjectingFontUniqueNameLookupTest : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
font_unique_name_lookup_ =
std::make_unique<FontUniqueNameLookup>(temp_dir_.GetPath());
font_unique_name_lookup_->SetFontFilePathsForTesting(
font_file_corruptor_.GetFontFilesList());
}
base::ScopedTempDir temp_dir_;
FontFileCorruptor font_file_corruptor_;
std::unique_ptr<FontUniqueNameLookup> font_unique_name_lookup_;
};
TEST_F(FaultInjectingFontUniqueNameLookupTest, TestZeroedTableContents) {
font_file_corruptor_.ZeroAfterTableIndex();
ASSERT_TRUE(font_unique_name_lookup_->UpdateTable());
FontTableMatcher matcher_after_update(
font_unique_name_lookup_->GetUniqueNameTableAsSharedMemoryRegion().Map());
ASSERT_EQ(matcher_after_update.AvailableFonts(), 0u);
}
TEST_F(FaultInjectingFontUniqueNameLookupTest, TestZeroedTableIndex) {
font_file_corruptor_.ZeroOutTableRecords();
ASSERT_TRUE(font_unique_name_lookup_->UpdateTable());
FontTableMatcher matcher_after_update(
font_unique_name_lookup_->GetUniqueNameTableAsSharedMemoryRegion().Map());
ASSERT_EQ(matcher_after_update.AvailableFonts(), 0u);
}
class FontUniqueNameLookupUpdateTest : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_TRUE(lookup_table_storage_dir.CreateUniqueTempDir());
font_unique_name_lookup_ = std::make_unique<FontUniqueNameLookup>(
lookup_table_storage_dir.GetPath());
font_unique_name_lookup_->SetFontFilePathsForTesting(
SplitFontFilesList(AndroidFontFilesList(), false));
font_unique_name_lookup_->SetAndroidBuildFingerprintForTesting("A");
}
base::ScopedTempDir lookup_table_storage_dir;
std::unique_ptr<FontUniqueNameLookup> font_unique_name_lookup_;
};
TEST_F(FontUniqueNameLookupUpdateTest, CompareSets) {
ASSERT_TRUE(font_unique_name_lookup_->UpdateTable());
FontTableMatcher matcher_initial(
font_unique_name_lookup_->GetUniqueNameTableAsSharedMemoryRegion().Map());
ASSERT_GT(matcher_initial.AvailableFonts(), 0u);
font_unique_name_lookup_->SetFontFilePathsForTesting(
SplitFontFilesList(AndroidFontFilesList(), true));
// Set the Android build fingerprint to something different from what it's set
// to in the test's SetUp method to trigger re-indexing.
font_unique_name_lookup_->SetAndroidBuildFingerprintForTesting("B");
font_unique_name_lookup_->UpdateTableIfNeeded();
FontTableMatcher matcher_second_half(
font_unique_name_lookup_->GetUniqueNameTableAsSharedMemoryRegion().Map());
ASSERT_GT(matcher_initial.AvailableFonts(), 0u);
ASSERT_TRUE(matcher_initial.FontListIsDisjointFrom(matcher_second_half));
}
} // namespace content
// 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.
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package content;
message FontUniqueNameTable {
// The Android build fingerprint for which this font list is stored.
required string stored_for_android_build_fp = 1;
message FontUniqueNameEntry {
required string file_path = 10;
required uint32 ttc_index = 20;
optional string full_name = 30;
optional string postscript_name = 40;
}
repeated FontUniqueNameEntry font_entries = 10;
}
\ No newline at end of file
// 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 "content/browser/font_unique_name_lookup/icu_fold_case_util.h"
using namespace ::icu_62;
namespace content {
std::string IcuFoldCase(const std::string& name_request) {
UnicodeString name_request_unicode = UnicodeString::fromUTF8(name_request);
name_request_unicode.foldCase();
std::string name_request_lower;
name_request_unicode.toUTF8String(name_request_lower);
return name_request_lower;
}
} // namespace content
// 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 CONTENT_BROWSER_FONT_UNIQUE_NAME_LOOKUP_ICU_FOLD_CASE_UTIL_H_
#define CONTENT_BROWSER_FONT_UNIQUE_NAME_LOOKUP_ICU_FOLD_CASE_UTIL_H_
#include "content/common/content_export.h"
#include "third_party/icu/source/common/unicode/unistr.h"
namespace content {
// Executes ICU's UnicodeString locale-independent foldCase method on
// |name_request| and returns a case folded string suitable for case-insensitive
// bitwise comparison. Used by FontTableMatcher and FontUniqueNameLookup for
// storing and comparing case folded font names.
std::string CONTENT_EXPORT IcuFoldCase(const std::string& name_request);
} // namespace content
#endif // CONTENT_BROWSER_FONT_UNIQUE_NAME_LOOKUP_ICU_FOLD_CASE_UTIL
// 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 "content/browser/font_unique_name_lookup/icu_fold_case_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
TEST(IcuFoldCaseUtilTest, FoldingExamples) {
ASSERT_EQ(IcuFoldCase("Roboto Condensed Bold Italic"),
IcuFoldCase("roboto condensed bold italic"));
ASSERT_EQ(IcuFoldCase("NotoSansDevanagariUI-Bold"),
IcuFoldCase("notosansdevanagariui-bold"));
ASSERT_EQ(IcuFoldCase(""), IcuFoldCase(""));
ASSERT_EQ(IcuFoldCase("12345"), IcuFoldCase("12345"));
ASSERT_EQ(IcuFoldCase("СКОРБЬ СХОДИТ ЩЕДРОТ"),
IcuFoldCase("скорбь сходит щедрот"));
}
} // namespace content
......@@ -2000,6 +2000,9 @@ test("content_unittests") {
"../browser/android/overscroll_controller_android_unittest.cc",
"../browser/android/scoped_surface_request_manager_unittest.cc",
"../browser/android/url_request_content_job_unittest.cc",
"../browser/font_unique_name_lookup/font_table_matcher_unittest.cc",
"../browser/font_unique_name_lookup/font_unique_name_lookup_unittest.cc",
"../browser/font_unique_name_lookup/icu_fold_case_util_unittest.cc",
"../browser/media/capture/screen_capture_device_android_unittest.cc",
"../renderer/java/gin_java_bridge_value_converter_unittest.cc",
"../renderer/media/android/stream_texture_wrapper_impl_unittest.cc",
......@@ -2012,6 +2015,8 @@ test("content_unittests") {
deps += [
"//base:base_java_unittest_support",
"//build/config/freetype",
"//content/browser/font_unique_name_lookup:font_unique_name_table_proto",
"//content/public/android:content_java",
"//media/capture/content/android",
"//media/capture/content/android:screen_capture_java",
......
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