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.
#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.
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